PART III b): German Credit Score Classification Model BIAS & FAIRNESS (Gender as protected variable).

By: Krishna J

Importing necessary libraries

In [1]:
import pandas as pd
import numpy as np
import seaborn               as sns
import matplotlib.pyplot     as plt
from sklearn.model_selection import train_test_split
#from sklearn.ensemble        import RandomForestClassifier
#from sklearn.linear_model    import LogisticRegression
from sklearn.preprocessing   import MinMaxScaler, StandardScaler
from sklearn.base            import TransformerMixin
from sklearn.pipeline        import Pipeline, FeatureUnion
from typing                  import List, Union, Dict
# Warnings will be used to silence various model warnings for tidier output
import warnings
warnings.filterwarnings('ignore')
%matplotlib inline 
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"
np.random.seed(0)

Importing source dataset

In [2]:
German_df = pd.read_csv('C:/Users/krish/Downloads/German-reduced.csv')

print(German_df.shape)
print (German_df.columns)
(1000, 24)
Index(['Gender', 'Age', 'Marital_Status', 'NumMonths', 'Savings_<500',
       'Savings_none', 'Dependents', 'Property_rent',
       'Job_management/self-emp/officer/highly qualif emp',
       'Debtors_guarantor', 'Purpose_CarNew', 'Purpose_furniture/equip',
       'CreditHistory_none/paid', 'Purpose_CarUsed', 'CreditAmount',
       'Collateral_real estate', 'Debtors_none',
       'Job_unemp/unskilled-non resident', 'Purpose_others',
       'CreditHistory_other', 'PayBackPercent', 'Collateral_unknown/none',
       'Purpose_education', 'CreditStatus'],
      dtype='object')
In [3]:
German_df.head()
Out[3]:
Gender Age Marital_Status NumMonths Savings_<500 Savings_none Dependents Property_rent Job_management/self-emp/officer/highly qualif emp Debtors_guarantor ... CreditAmount Collateral_real estate Debtors_none Job_unemp/unskilled-non resident Purpose_others CreditHistory_other PayBackPercent Collateral_unknown/none Purpose_education CreditStatus
0 1 1 1 6 0 1 1 0 0 0 ... 0.050567 1 1 0 0 1 4 0 0 1
1 0 0 0 48 1 0 1 0 0 0 ... 0.313690 1 1 0 0 0 2 0 0 0
2 1 1 1 12 1 0 2 0 0 0 ... 0.101574 1 1 0 0 1 2 0 1 1
3 1 1 1 42 1 0 2 0 0 1 ... 0.419941 0 0 0 0 0 2 0 0 1
4 1 1 1 24 1 0 2 0 0 0 ... 0.254209 0 1 0 0 0 3 1 0 0

5 rows × 24 columns

In [4]:
#feature_list = ['Gender','Age','Marital_Status','NumMonths','Savings_<500','Savings_none','Dependents','Property_rent','Job_management/self-emp/officer/highly qualif emp','Debtors_guarantor','Purpose_CarNew',                           'Purpose_furniture/equip','CreditHistory_none/paid','Purpose_CarUsed','CreditAmount','CreditStatus']
feature_list=['Gender','Age','Marital_Status','NumMonths','Savings_<500','Savings_none','Dependents','Property_rent',
                           'Job_management/self-emp/officer/highly qualif emp','Debtors_guarantor','Purpose_CarNew',
                           'Purpose_furniture/equip','CreditHistory_none/paid','Purpose_CarUsed','CreditAmount',
                           'Collateral_real estate','Debtors_none','Job_unemp/unskilled-non resident','Purpose_others',             
                            'CreditHistory_other','PayBackPercent','Collateral_unknown/none','Purpose_education', 'CreditStatus']
In [5]:
X = German_df.iloc[:, :-1]
y = German_df['CreditStatus']
X.head()
y.head()
Out[5]:
Gender Age Marital_Status NumMonths Savings_<500 Savings_none Dependents Property_rent Job_management/self-emp/officer/highly qualif emp Debtors_guarantor ... Purpose_CarUsed CreditAmount Collateral_real estate Debtors_none Job_unemp/unskilled-non resident Purpose_others CreditHistory_other PayBackPercent Collateral_unknown/none Purpose_education
0 1 1 1 6 0 1 1 0 0 0 ... 0 0.050567 1 1 0 0 1 4 0 0
1 0 0 0 48 1 0 1 0 0 0 ... 0 0.313690 1 1 0 0 0 2 0 0
2 1 1 1 12 1 0 2 0 0 0 ... 0 0.101574 1 1 0 0 1 2 0 1
3 1 1 1 42 1 0 2 0 0 1 ... 0 0.419941 0 0 0 0 0 2 0 0
4 1 1 1 24 1 0 2 0 0 0 ... 0 0.254209 0 1 0 0 0 3 1 0

5 rows × 23 columns

Out[5]:
0    1
1    0
2    1
3    1
4    0
Name: CreditStatus, dtype: int64

from imblearn.over_sampling import ADASYN from collections import Counter

ada = ADASYN(random_state=40) print('Original dataset shape {}'.format(Counter(y))) X_res, y_res = ada.fit_resample(X,y) print('Resampled dataset shape {}'.format(Counter(y_res)))

German_df=X = pd.DataFrame(np.column_stack((X_res, y_res)))

In [6]:
German_df.head()
Out[6]:
Gender Age Marital_Status NumMonths Savings_<500 Savings_none Dependents Property_rent Job_management/self-emp/officer/highly qualif emp Debtors_guarantor ... CreditAmount Collateral_real estate Debtors_none Job_unemp/unskilled-non resident Purpose_others CreditHistory_other PayBackPercent Collateral_unknown/none Purpose_education CreditStatus
0 1 1 1 6 0 1 1 0 0 0 ... 0.050567 1 1 0 0 1 4 0 0 1
1 0 0 0 48 1 0 1 0 0 0 ... 0.313690 1 1 0 0 0 2 0 0 0
2 1 1 1 12 1 0 2 0 0 0 ... 0.101574 1 1 0 0 1 2 0 1 1
3 1 1 1 42 1 0 2 0 0 1 ... 0.419941 0 0 0 0 0 2 0 0 1
4 1 1 1 24 1 0 2 0 0 0 ... 0.254209 0 1 0 0 0 3 1 0 0

5 rows × 24 columns

In [7]:
German_df.columns=feature_list
German_df.head()
Out[7]:
Gender Age Marital_Status NumMonths Savings_<500 Savings_none Dependents Property_rent Job_management/self-emp/officer/highly qualif emp Debtors_guarantor ... CreditAmount Collateral_real estate Debtors_none Job_unemp/unskilled-non resident Purpose_others CreditHistory_other PayBackPercent Collateral_unknown/none Purpose_education CreditStatus
0 1 1 1 6 0 1 1 0 0 0 ... 0.050567 1 1 0 0 1 4 0 0 1
1 0 0 0 48 1 0 1 0 0 0 ... 0.313690 1 1 0 0 0 2 0 0 0
2 1 1 1 12 1 0 2 0 0 0 ... 0.101574 1 1 0 0 1 2 0 1 1
3 1 1 1 42 1 0 2 0 0 1 ... 0.419941 0 0 0 0 0 2 0 0 1
4 1 1 1 24 1 0 2 0 0 0 ... 0.254209 0 1 0 0 0 3 1 0 0

5 rows × 24 columns

Metrics to calculate model fairness necessary libraries

In [8]:
from aif360.datasets import GermanDataset
from aif360.metrics import BinaryLabelDatasetMetric

def fair_metrics(fname, dataset, pred, pred_is_dataset=False):
    filename = fname
    if pred_is_dataset:
        dataset_pred = pred
    else:
        dataset_pred = dataset.copy()
        dataset_pred.labels = pred

    cols = ['Accuracy', 'F1', 'DI','SPD', 'EOD', 'AOD', 'ERD', 'CNT', 'TI']
    obj_fairness = [[1,1,1,0,0,0,0,1,0]]

    fair_metrics = pd.DataFrame(data=obj_fairness, index=['objective'], columns=cols)

    for attr in dataset_pred.protected_attribute_names:
        idx = dataset_pred.protected_attribute_names.index(attr)
        privileged_groups =  [{attr:dataset_pred.privileged_protected_attributes[idx][0]}]
        unprivileged_groups = [{attr:dataset_pred.unprivileged_protected_attributes[idx][0]}]

        classified_metric = ClassificationMetric(dataset,
                                                     dataset_pred,
                                                     unprivileged_groups=unprivileged_groups,
                                                     privileged_groups=privileged_groups)

        metric_pred = BinaryLabelDatasetMetric(dataset_pred,
                                                     unprivileged_groups=unprivileged_groups,
                                                     privileged_groups=privileged_groups)

        distortion_metric = SampleDistortionMetric(dataset,
                                                     dataset_pred,
                                                     unprivileged_groups=unprivileged_groups,
                                                     privileged_groups=privileged_groups)

        acc = classified_metric.accuracy()
        f1_sc = 2 * (classified_metric.precision() * classified_metric.recall()) / (classified_metric.precision() + classified_metric.recall())

        mt = [acc, f1_sc,
                        classified_metric.disparate_impact(),
                        classified_metric.mean_difference(),
                        classified_metric.equal_opportunity_difference(),
                        classified_metric.average_odds_difference(),
                        classified_metric.error_rate_difference(),
                        metric_pred.consistency(),
                        classified_metric.theil_index()
                    ]
        w_row = []
        print('Computing fairness of the model.')
        for i in mt:
            #print("%.8f"%i)
            w_row.append("%.8f"%i)
        with open(filename, 'a') as csvfile:
            csvwriter = csv.writer(csvfile)
            csvwriter.writerow(w_row)
        row = pd.DataFrame([mt],
                           columns  = cols,
                           index = [attr]
                          )
        fair_metrics = fair_metrics.append(row)
    fair_metrics = fair_metrics.replace([-np.inf, np.inf], 2)
    return fair_metrics

def get_fair_metrics_and_plot(fname, data, model, plot=False, model_aif=False):
    pred = model.predict(data).labels if model_aif else model.predict(data.features)
    fair = fair_metrics(fname, data, pred)
    if plot:
        pass

    return fair

def get_model_performance(X_test, y_true, y_pred, probs):
    accuracy = accuracy_score(y_true, y_pred)
    matrix = confusion_matrix(y_true, y_pred)
    f1 = f1_score(y_true, y_pred)
    return accuracy, matrix, f1

def plot_model_performance(model, X_test, y_true):
    y_pred = model.predict(X_test)
    probs = model.predict_proba(X_test)
    accuracy, matrix, f1 = get_model_performance(X_test, y_true, y_pred, probs)

Local file to load metric values

In [9]:
filename= 'C:/Users/krish/Downloads/filename_mainpjt_results_apr_19_gender_na.csv'

Converting data to aif compatible format

Since we are dealing with binary label dataset we are using aif360 class BiaryLabelDataset here with target label as CreditStatus and protected attributes as age,gender,marital status. Refer part 11 for more details on protected attributes and privileged classes.

In [10]:
# Fairness metrics
from aif360.metrics import BinaryLabelDatasetMetric
from aif360.explainers import MetricTextExplainer
from aif360.metrics import ClassificationMetric
# Get DF into IBM format
from aif360 import datasets
#converting to aif dataset
aif_dataset = datasets.BinaryLabelDataset(favorable_label = 1, unfavorable_label = 0, df=German_df,
                                                      label_names=["CreditStatus"],
                                                     protected_attribute_names=["Gender"],
                                              privileged_protected_attributes = [1])
In [11]:
#dataset_orig = GermanDataset(protected_attribute_names=['sex'],
#                            privileged_classes=[[1]],
#                            features_to_keep=['age', 'sex', 'employment', 'housing', 'savings', 'credit_amount', 'month', 'purpose'],
#                            custom_preprocessing=custom_preprocessing)

Splitting data to train and test sets

In [12]:
#privileged_groups = [{'Age':1},{'Gender': 1},{'Marital_Status':1}]
#unprivileged_groups = [{'Age':0},{'Gender': 0},{'Marital_Status':0}]
In [13]:
privileged_groups = [{'Gender': 1}]
unprivileged_groups = [{'Gender': 0}]
In [14]:
data_orig_train, data_orig_test = aif_dataset.split([0.7], shuffle=True)

X_train = data_orig_train.features
y_train = data_orig_train.labels.ravel()

X_test = data_orig_test.features
y_test = data_orig_test.labels.ravel()
In [15]:
X_train.shape
X_test.shape
Out[15]:
(700, 23)
Out[15]:
(300, 23)
In [16]:
data_orig_test.labels[:10].ravel()
Out[16]:
array([0., 0., 1., 1., 1., 0., 1., 1., 1., 1.])
In [17]:
data_orig_train.labels[:10].ravel()
Out[17]:
array([1., 1., 1., 1., 1., 1., 1., 1., 1., 0.])
In [288]:
metric_orig_train = BinaryLabelDatasetMetric(data_orig_train, 
                                             unprivileged_groups=unprivileged_groups,
                                             privileged_groups=privileged_groups)
print("Difference in mean outcomes between unprivileged and privileged groups = %f" % metric_orig_train.mean_difference())
Difference in mean outcomes between unprivileged and privileged groups = -0.108070

Building ML model

Considering ensemble models for our study.

1. RANDOM FOREST CLASSIFIER MODEL

In [18]:
#Seting the Hyper Parameters
param_grid = {"max_depth": [3,5,7, 10,None],
              "n_estimators":[3,5,10,25,50,150],
              "max_features": [4,7,15,20]}
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV
#Creating the classifier
rf_model = RandomForestClassifier(random_state=40)
grid_search = GridSearchCV(rf_model, param_grid=param_grid, cv=5, scoring='recall', verbose=0)
model_rf = grid_search
In [19]:
mdl_rf = model_rf.fit(data_orig_train.features, data_orig_train.labels.ravel())
In [20]:
from sklearn.metrics import confusion_matrix
conf_mat_rf = confusion_matrix(data_orig_test.labels.ravel(), model_rf.predict(data_orig_test.features))
conf_mat_rf
from sklearn.metrics import accuracy_score
print(accuracy_score(data_orig_test.labels.ravel(), model_rf.predict(data_orig_test.features)))
Out[20]:
array([[  3,  87],
       [  0, 210]], dtype=int64)
0.71
In [21]:
unique, counts = np.unique(data_orig_test.labels.ravel(), return_counts=True)
dict(zip(unique, counts))
Out[21]:
{0.0: 90, 1.0: 210}

1.a. Feature importance of model

In [22]:
importances = model_rf.best_estimator_.feature_importances_
indices = np.argsort(importances)
features = data_orig_train.feature_names
#https://stackoverflow.com/questions/48377296/get-feature-importance-from-gridsearchcv
In [23]:
importances
Out[23]:
array([0.03339163, 0.06129979, 0.03458668, 0.1549452 , 0.08652746,
       0.06627319, 0.00340989, 0.0251218 , 0.01348329, 0.00826302,
       0.06000396, 0.00363823, 0.07900086, 0.0084767 , 0.17899456,
       0.01492402, 0.0018684 , 0.01691877, 0.00576587, 0.09671483,
       0.02132972, 0.0071254 , 0.01793675])
In [24]:
importances[indices]
Out[24]:
array([0.0018684 , 0.00340989, 0.00363823, 0.00576587, 0.0071254 ,
       0.00826302, 0.0084767 , 0.01348329, 0.01492402, 0.01691877,
       0.01793675, 0.02132972, 0.0251218 , 0.03339163, 0.03458668,
       0.06000396, 0.06129979, 0.06627319, 0.07900086, 0.08652746,
       0.09671483, 0.1549452 , 0.17899456])
In [25]:
features
Out[25]:
['Gender',
 'Age',
 'Marital_Status',
 'NumMonths',
 'Savings_<500',
 'Savings_none',
 'Dependents',
 'Property_rent',
 'Job_management/self-emp/officer/highly qualif emp',
 'Debtors_guarantor',
 'Purpose_CarNew',
 'Purpose_furniture/equip',
 'CreditHistory_none/paid',
 'Purpose_CarUsed',
 'CreditAmount',
 'Collateral_real estate',
 'Debtors_none',
 'Job_unemp/unskilled-non resident',
 'Purpose_others',
 'CreditHistory_other',
 'PayBackPercent',
 'Collateral_unknown/none',
 'Purpose_education']
In [26]:
plt.figure(figsize=(20,30))
plt.title('Feature Importances')
plt.barh(range(len(indices)), importances[indices], color='b', align='center')
plt.yticks(range(len(indices)), [features[i] for i in indices])
plt.xlabel('Relative Importance')
plt.show()
Out[26]:
<Figure size 1440x2160 with 0 Axes>
Out[26]:
Text(0.5, 1.0, 'Feature Importances')
Out[26]:
<BarContainer object of 23 artists>
Out[26]:
([<matplotlib.axis.YTick at 0x1e4dcef2088>,
  <matplotlib.axis.YTick at 0x1e4dce92608>,
  <matplotlib.axis.YTick at 0x1e4dce5b5c8>,
  <matplotlib.axis.YTick at 0x1e4def48788>,
  <matplotlib.axis.YTick at 0x1e4def4d408>,
  <matplotlib.axis.YTick at 0x1e4def4da08>,
  <matplotlib.axis.YTick at 0x1e4def520c8>,
  <matplotlib.axis.YTick at 0x1e4def52808>,
  <matplotlib.axis.YTick at 0x1e4def570c8>,
  <matplotlib.axis.YTick at 0x1e4def57848>,
  <matplotlib.axis.YTick at 0x1e4def5c108>,
  <matplotlib.axis.YTick at 0x1e4def5ca88>,
  <matplotlib.axis.YTick at 0x1e4def604c8>,
  <matplotlib.axis.YTick at 0x1e4def60d88>,
  <matplotlib.axis.YTick at 0x1e4def63908>,
  <matplotlib.axis.YTick at 0x1e4def605c8>,
  <matplotlib.axis.YTick at 0x1e4def52f08>,
  <matplotlib.axis.YTick at 0x1e4def68588>,
  <matplotlib.axis.YTick at 0x1e4def6b248>,
  <matplotlib.axis.YTick at 0x1e4def6ba48>,
  <matplotlib.axis.YTick at 0x1e4def6e4c8>,
  <matplotlib.axis.YTick at 0x1e4def72088>,
  <matplotlib.axis.YTick at 0x1e4def72a88>],
 <a list of 23 Text yticklabel objects>)
Out[26]:
Text(0.5, 0, 'Relative Importance')

1.b. Model Explainability/interpretability

1.b.1 Using SHAP (SHapley Additive exPlanations)

In [27]:
import shap

Test data interpretation

In [28]:
rf_explainer = shap.KernelExplainer(model_rf.predict, data_orig_test.features)
rf_shap_values = rf_explainer.shap_values(data_orig_test.features,nsamples=50)
#https://towardsdatascience.com/explain-any-models-with-the-shap-values-use-the-kernelexplainer-79de9464897a
Using 300 background data samples could cause slower run times. Consider using shap.sample(data, K) or shap.kmeans(data, K) to summarize the background as K samples.

In [29]:
rf_shap_values
Out[29]:
array([[ 0.        ,  0.00208812,  0.        , ...,  0.        ,
         0.        ,  0.        ],
       [ 0.        ,  0.        ,  0.02612348, ...,  0.        ,
        -0.00539599,  0.        ],
       [ 0.00111976,  0.00137532,  0.        , ...,  0.        ,
         0.        ,  0.        ],
       ...,
       [ 0.00029608,  0.00013956,  0.00010948, ...,  0.        ,
         0.        ,  0.00153509],
       [ 0.        ,  0.00067686,  0.        , ...,  0.        ,
         0.        ,  0.        ],
       [ 0.        ,  0.00265505,  0.        , ...,  0.        ,
         0.        ,  0.        ]])
In [30]:
rf_explainer.expected_value
Out[30]:
0.9900000000000001
In [31]:
y_test_predict=model_rf.predict(data_orig_test.features)
y_test_predict[:12]
data_orig_test.labels[:12].ravel()
data_orig_test.features[:2,:]
Out[31]:
array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])
Out[31]:
array([0., 0., 1., 1., 1., 0., 1., 1., 1., 1., 1., 1.])
Out[31]:
array([[1.00000000e+00, 1.00000000e+00, 0.00000000e+00, 2.40000000e+01,
        1.00000000e+00, 0.00000000e+00, 1.00000000e+00, 0.00000000e+00,
        0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,
        0.00000000e+00, 0.00000000e+00, 4.25883130e-02, 1.00000000e+00,
        1.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,
        4.00000000e+00, 0.00000000e+00, 0.00000000e+00],
       [1.00000000e+00, 1.00000000e+00, 1.00000000e+00, 6.00000000e+01,
        1.00000000e+00, 0.00000000e+00, 1.00000000e+00, 0.00000000e+00,
        0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,
        0.00000000e+00, 0.00000000e+00, 3.62385826e-01, 0.00000000e+00,
        1.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,
        3.00000000e+00, 1.00000000e+00, 0.00000000e+00]])
In [32]:
y_test_predict.mean()
Out[32]:
0.99

The explainer expected value is the average model predicted value on input data. Shapely helps to understand how individual features impact the output of each individual instance. The shapely values are model predicted values which may not coincide with actual y test values due to prediction error.

link=”logit” argument converts the logit values to probability

In [33]:
shap.initjs()
shap.force_plot(rf_explainer.expected_value,rf_shap_values[0],data_orig_test.features[0],data_orig_test.feature_names,link='logit')
#https://github.com/slundberg/shap
#https://github.com/slundberg/shap/issues/279
#https://github.com/slundberg/shap/issues/977
shap.initjs()
shap.force_plot(rf_explainer.expected_value,rf_shap_values[0],data_orig_test.features[0],data_orig_test.feature_names)
Out[33]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
Out[33]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.

Features in blue pushes the base value towards lowest values and features in red moves base levels towards higher values.

Shapley values calculate the importance of a feature by comparing what a model predicts with and without the feature. However, since the order in which a model sees features can affect its predictions, this is done in every possible order, so that the features are fairly compared.

The SHAP plot shows features that contribute to pushing the output from the base value (average model output) to the actual predicted value.

In [34]:
shap.initjs()
shap.force_plot(rf_explainer.expected_value,rf_shap_values[1], data_orig_test.features[1],data_orig_test.feature_names,link='logit')
shap.initjs()
shap.force_plot(rf_explainer.expected_value,rf_shap_values[1], data_orig_test.features[1],data_orig_test.feature_names)
Out[34]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
Out[34]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [35]:
data_orig_test.feature_names
Out[35]:
['Gender',
 'Age',
 'Marital_Status',
 'NumMonths',
 'Savings_<500',
 'Savings_none',
 'Dependents',
 'Property_rent',
 'Job_management/self-emp/officer/highly qualif emp',
 'Debtors_guarantor',
 'Purpose_CarNew',
 'Purpose_furniture/equip',
 'CreditHistory_none/paid',
 'Purpose_CarUsed',
 'CreditAmount',
 'Collateral_real estate',
 'Debtors_none',
 'Job_unemp/unskilled-non resident',
 'Purpose_others',
 'CreditHistory_other',
 'PayBackPercent',
 'Collateral_unknown/none',
 'Purpose_education']
In [36]:
shap.force_plot(rf_explainer.expected_value,
                rf_shap_values, data_orig_test.features[:,:],feature_names = data_orig_test.feature_names)
Out[36]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [37]:
p = shap.summary_plot(rf_shap_values, data_orig_test.features, feature_names=data_orig_test.feature_names,plot_type="bar") 
display(p)
None

Variables with higher impact are displayed at the credit history, credit amount,num of months.

In [38]:
shap.decision_plot(rf_explainer.expected_value, rf_shap_values,feature_names=data_orig_test.feature_names)
  • The x-axis represents the model's output. In this case, the units are log odds.
  • The plot is centered on the x-axis at explainer.expected_value.
  • All SHAP values are relative to the model's expected value like a linear model's effects are relative to the intercept.
  • The y-axis lists the model's features.
  • By default, the features are ordered by descending importance. The importance is calculated over the observations plotted. This is usually different than the importance ordering for the entire dataset.
  • In addition to feature importance ordering, the decision plot also supports hierarchical cluster feature ordering and user-defined feature ordering.
  • Each observation's prediction is represented by a colored line. At the top of the plot, each line strikes the x-axis at its corresponding observation's predicted value. This value determines the color of the line on a spectrum.
  • Moving from the bottom of the plot to the top, SHAP values for each feature are added to the model's base value. This shows how each feature contributes to the overall prediction.
  • At the bottom of the plot, the observations converge at explainer.expected_value https://slundberg.github.io/shap/notebooks/plots/decision_plot.html

Like the force plot, the decision plot supports link='logit' to transform log odds to probabilities.

In [39]:
shap.decision_plot(rf_explainer.expected_value, rf_shap_values,feature_names=data_orig_test.feature_names,link='logit')
In [40]:
shap.plots._waterfall.waterfall_legacy(rf_explainer.expected_value, rf_shap_values[0],feature_names=data_orig_test.feature_names)

For first instace of input,out of all the displayed variables, CreditHistory with value other is playing major role is pushing the target variable outcome towards predicting 1.

Interpretation of graph: https://shap.readthedocs.io/en/latest/example_notebooks/overviews/An%20introduction%20to%20explainable%20AI%20with%20Shapley%20values.html

f(x)- model output impacted by features; E(f(x))- expected output.

One the fundemental properties of Shapley values is that they always sum up to the difference between the game outcome when all players are present and the game outcome when no players are present. For machine learning models this means that SHAP values of all the input features will always sum up to the difference between baseline (expected) model output and the current model output for the prediction being explained.

Shapley values calculate the importance of a feature by comparing what a model predicts with and without the feature. However, since the order in which a model sees features can affect its predictions, this is done in every possible order, so that the features are fairly compared. https://medium.com/@gabrieltseng/interpreting-complex-models-with-shap-values-1c187db6ec83

In [41]:
shap.plots._waterfall.waterfall_legacy(rf_explainer.expected_value, rf_shap_values[1],feature_names=data_orig_test.feature_names)

For second instace of input,out of all the displayed variables, credit amount is playing major role is pushing the target variable outcome towards predicting 0.

1.b.2 Using ELI5

In [42]:
#!pip install eli5
import eli5
from eli5.sklearn import PermutationImportance
In [43]:
perm_rf = PermutationImportance(mdl_rf).fit(data_orig_test.features, data_orig_test.labels.ravel())

Feature Importance

In [44]:
perm_imp_1=eli5.show_weights(perm_rf,feature_names = data_orig_test.feature_names)
perm_imp_1
plt.show()
Out[44]:
Weight Feature
0.0038 ± 0.0038 Savings_none
0.0038 ± 0.0071 Purpose_CarNew
0.0029 ± 0.0047 CreditHistory_none/paid
0.0029 ± 0.0047 Savings_<500
0.0019 ± 0.0047 CreditAmount
0.0019 ± 0.0047 Age
0.0019 ± 0.0047 Gender
0.0010 ± 0.0038 Marital_Status
0.0010 ± 0.0038 Property_rent
0.0010 ± 0.0038 NumMonths
0 ± 0.0000 Collateral_real estate
0 ± 0.0000 Collateral_unknown/none
0 ± 0.0000 Debtors_guarantor
0 ± 0.0000 PayBackPercent
0 ± 0.0000 Job_management/self-emp/officer/highly qualif emp
0 ± 0.0000 Dependents
0 ± 0.0000 Purpose_furniture/equip
0 ± 0.0000 Purpose_CarUsed
0 ± 0.0000 Purpose_education
0 ± 0.0000 Job_unemp/unskilled-non resident
… 3 more …
  • eli5 provides a way to compute feature importances for any black-box estimator by measuring how score decreases when a feature is not available; the method is also known as “permutation importance” or “Mean Decrease Accuracy (MDA)”.
  • The first number in each row shows how much model performance decreased with a random shuffling (in this case, using "accuracy" as the performance metric).

  • Like most things in data science, there is some randomness to the exact performance change from a shuffling a column. We measure the amount of randomness in our permutation importance calculation by repeating the process with multiple shuffles. The number after the ± measures how performance varied from one-reshuffling to the next.

  • You'll occasionally see negative values for permutation importances. In those cases, the predictions on the shuffled (or noisy) data happened to be more accurate than the real data. This happens when the feature didn't matter (should have had an importance close to 0), but random chance caused the predictions on shuffled data to be more accurate. This is more common with small datasets, like the one in this example, because there is more room for luck/chance.

https://www.kaggle.com/dansbecker/permutation-importance

1.c. Measuring fairness

Of Baseline model

In [45]:
import pandas as pd
import csv
import os
import numpy as np
import sys
from aif360.metrics import *
from sklearn.metrics import confusion_matrix, accuracy_score, f1_score, roc_curve, auc
plot_model_performance(mdl_rf, X_test, y_test)
In [46]:
fair_rf = get_fair_metrics_and_plot(filename, data_orig_test, mdl_rf)
fair_rf
Computing fairness of the model.
Out[46]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.00 1.000000 1.000000 0.000000 0.0 0.000000 0.00000 1.000000 0.000000
Gender 0.71 0.828402 0.997514 -0.002462 0.0 -0.004615 -0.01067 0.987333 0.057005
In [47]:
type(data_orig_train)
Out[47]:
aif360.datasets.binary_label_dataset.BinaryLabelDataset

PRE PROCESSING

In [48]:
### Reweighing
from aif360.algorithms.preprocessing import Reweighing

RW_rf = Reweighing(unprivileged_groups=unprivileged_groups, privileged_groups=privileged_groups)

data_transf_train_rf_rw = RW_rf.fit_transform(data_orig_train)

#train and save model
rf_transf_rw = model_rf.fit(data_transf_train_rf_rw.features,
                     data_transf_train_rf_rw.labels.ravel())

data_transf_test_rf_rw = RW_rf.transform(data_orig_test)
fair_rf_rw = get_fair_metrics_and_plot(filename, data_transf_test_rf_rw, rf_transf_rw, plot=False)
WARNING:root:No module named 'numba.decorators': LFR will be unavailable. To install, run:
pip install 'aif360[LFR]'
Computing fairness of the model.
In [289]:
metric_transf_train = BinaryLabelDatasetMetric(data_transf_train_rf_rw, 
                                               unprivileged_groups=unprivileged_groups,
                                               privileged_groups=privileged_groups)
print("Difference in mean outcomes between unprivileged and privileged groups = %f" % metric_transf_train.mean_difference())
Difference in mean outcomes between unprivileged and privileged groups = 0.000000
In [49]:
fair_rf_rw
Out[49]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.00000 0.000000 0.0 0.000000 0.000000 1.000000 0.000000
Gender 0.701725 0.822616 1.00124 0.001227 0.0 -0.004615 -0.107827 0.987333 0.057005
In [50]:
from aif360.algorithms.preprocessing import DisparateImpactRemover

DIR_rf = DisparateImpactRemover()
data_transf_train_rf_dir = DIR_rf.fit_transform(data_orig_train)

# Train and save the model
rf_transf_dir = model_rf.fit(data_transf_train_rf_dir.features,data_transf_train_rf_dir.labels.ravel())
In [51]:
fair_dir_rf_dir = get_fair_metrics_and_plot(filename,data_orig_test, rf_transf_dir, plot=False)
fair_dir_rf_dir
Computing fairness of the model.
Out[51]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000 0.000000
Gender 0.706667 0.825397 1.011765 0.011491 0.013333 0.009744 -0.015321 0.976 0.063612

INPROCESSING

In [52]:
#!pip install --user --upgrade tensorflow==1.15.0
#2.2.0
#!pip uninstall tensorflow
In [53]:
#!pip install "tensorflow==1.15"
#!pip install --upgrade tensorflow-hub
In [54]:
#%tensorflow_version 1.15
import tensorflow  as tf
#from tensorflow.compat.v1 import variable_scope
print('Using TensorFlow version', tf.__version__)
Using TensorFlow version 1.15.0
In [55]:
#sess = tf.compat.v1.Session()
#import tensorflow as tf

sess = tf.compat.v1.Session()
In [56]:
#import tensorflow as tf
#sess = tf.Session()
tf.compat.v1.reset_default_graph()
In [57]:
from aif360.algorithms.inprocessing.adversarial_debiasing import AdversarialDebiasing
#with tf.variable_scope('debiased_classifier',reuse=tf.AUTO_REUSE):
with tf.compat.v1.Session() as sess:
    with tf.variable_scope('scope1',reuse=tf.AUTO_REUSE) as scope:
        debiased_model_rf_ad = AdversarialDebiasing(privileged_groups = privileged_groups,
                          unprivileged_groups = unprivileged_groups,
                          scope_name=scope,
                          num_epochs=10,
                          debias=True,
                          sess=sess)
#train and save the model
        debiased_model_rf_ad.fit(data_orig_train)
        fair_rf_ad = get_fair_metrics_and_plot(filename, data_orig_test, debiased_model_rf_ad, plot=False, model_aif=True)
WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\aif360\algorithms\inprocessing\adversarial_debiasing.py:141: The name tf.placeholder is deprecated. Please use tf.compat.v1.placeholder instead.

WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\aif360\algorithms\inprocessing\adversarial_debiasing.py:141: The name tf.placeholder is deprecated. Please use tf.compat.v1.placeholder instead.

WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\aif360\algorithms\inprocessing\adversarial_debiasing.py:84: The name tf.get_variable is deprecated. Please use tf.compat.v1.get_variable instead.

WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\aif360\algorithms\inprocessing\adversarial_debiasing.py:84: The name tf.get_variable is deprecated. Please use tf.compat.v1.get_variable instead.

WARNING:tensorflow:
The TensorFlow contrib module will not be included in TensorFlow 2.0.
For more information, please see:
  * https://github.com/tensorflow/community/blob/master/rfcs/20180907-contrib-sunset.md
  * https://github.com/tensorflow/addons
  * https://github.com/tensorflow/io (for I/O related ops)
If you depend on functionality not listed there, please file an issue.

WARNING:tensorflow:
The TensorFlow contrib module will not be included in TensorFlow 2.0.
For more information, please see:
  * https://github.com/tensorflow/community/blob/master/rfcs/20180907-contrib-sunset.md
  * https://github.com/tensorflow/addons
  * https://github.com/tensorflow/io (for I/O related ops)
If you depend on functionality not listed there, please file an issue.

WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\aif360\algorithms\inprocessing\adversarial_debiasing.py:89: calling dropout (from tensorflow.python.ops.nn_ops) with keep_prob is deprecated and will be removed in a future version.
Instructions for updating:
Please use `rate` instead of `keep_prob`. Rate should be set to `rate = 1 - keep_prob`.
WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\aif360\algorithms\inprocessing\adversarial_debiasing.py:89: calling dropout (from tensorflow.python.ops.nn_ops) with keep_prob is deprecated and will be removed in a future version.
Instructions for updating:
Please use `rate` instead of `keep_prob`. Rate should be set to `rate = 1 - keep_prob`.
WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\tensorflow_core\python\ops\nn_impl.py:183: where (from tensorflow.python.ops.array_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.where in 2.0, which has the same broadcast rule as np.where
WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\tensorflow_core\python\ops\nn_impl.py:183: where (from tensorflow.python.ops.array_ops) is deprecated and will be removed in a future version.
Instructions for updating:
Use tf.where in 2.0, which has the same broadcast rule as np.where
WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\aif360\algorithms\inprocessing\adversarial_debiasing.py:159: The name tf.train.exponential_decay is deprecated. Please use tf.compat.v1.train.exponential_decay instead.

WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\aif360\algorithms\inprocessing\adversarial_debiasing.py:159: The name tf.train.exponential_decay is deprecated. Please use tf.compat.v1.train.exponential_decay instead.

WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\aif360\algorithms\inprocessing\adversarial_debiasing.py:161: The name tf.train.AdamOptimizer is deprecated. Please use tf.compat.v1.train.AdamOptimizer instead.

WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\aif360\algorithms\inprocessing\adversarial_debiasing.py:161: The name tf.train.AdamOptimizer is deprecated. Please use tf.compat.v1.train.AdamOptimizer instead.

WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\aif360\algorithms\inprocessing\adversarial_debiasing.py:165: The name tf.trainable_variables is deprecated. Please use tf.compat.v1.trainable_variables instead.

WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\aif360\algorithms\inprocessing\adversarial_debiasing.py:165: The name tf.trainable_variables is deprecated. Please use tf.compat.v1.trainable_variables instead.

WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\aif360\algorithms\inprocessing\adversarial_debiasing.py:187: The name tf.global_variables_initializer is deprecated. Please use tf.compat.v1.global_variables_initializer instead.

WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\aif360\algorithms\inprocessing\adversarial_debiasing.py:187: The name tf.global_variables_initializer is deprecated. Please use tf.compat.v1.global_variables_initializer instead.

WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\aif360\algorithms\inprocessing\adversarial_debiasing.py:188: The name tf.local_variables_initializer is deprecated. Please use tf.compat.v1.local_variables_initializer instead.

WARNING:tensorflow:From C:\Users\krish\Anaconda3\lib\site-packages\aif360\algorithms\inprocessing\adversarial_debiasing.py:188: The name tf.local_variables_initializer is deprecated. Please use tf.compat.v1.local_variables_initializer instead.

epoch 0; iter: 0; batch classifier loss: 2.422199; batch adversarial loss: 0.693444
epoch 1; iter: 0; batch classifier loss: 1.828090; batch adversarial loss: 0.699031
epoch 2; iter: 0; batch classifier loss: 1.056355; batch adversarial loss: 0.688084
epoch 3; iter: 0; batch classifier loss: 0.876389; batch adversarial loss: 0.758287
epoch 4; iter: 0; batch classifier loss: 0.751675; batch adversarial loss: 0.751926
epoch 5; iter: 0; batch classifier loss: 0.903647; batch adversarial loss: 0.769787
epoch 6; iter: 0; batch classifier loss: 0.775932; batch adversarial loss: 0.766653
epoch 7; iter: 0; batch classifier loss: 0.800803; batch adversarial loss: 0.760687
epoch 8; iter: 0; batch classifier loss: 0.722945; batch adversarial loss: 0.694430
epoch 9; iter: 0; batch classifier loss: 0.849798; batch adversarial loss: 0.786264
Out[57]:
<aif360.algorithms.inprocessing.adversarial_debiasing.AdversarialDebiasing at 0x1e4e3ed2388>
Computing fairness of the model.
In [58]:
fair_rf_ad
Out[58]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.0 1.000000 1.0 0.0 0.0 0.0 0.000000 1 0.00000
Gender 0.7 0.823529 1.0 0.0 0.0 0.0 -0.008208 [1.0] 0.05755
In [59]:
from aif360.algorithms.inprocessing import PrejudiceRemover
debiased_model_pr_rf = PrejudiceRemover()

# Train and save the model
debiased_model_pr_rf.fit(data_orig_train)

fair_rf_pr = get_fair_metrics_and_plot(filename, data_orig_test, debiased_model_pr_rf, plot=False, model_aif=True)
fair_rf_pr
Out[59]:
<aif360.algorithms.inprocessing.prejudice_remover.PrejudiceRemover at 0x1e4e316e788>
Computing fairness of the model.
Out[59]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.00 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Gender 0.71 0.814499 0.796059 -0.186867 -0.176667 -0.196026 0.054993 [0.874] 0.118819
#
In [60]:
y_pred = debiased_model_pr_rf.predict(data_orig_test)


data_orig_test_pred = data_orig_test.copy(deepcopy=True)
In [61]:
# Prediction with the original RandomForest model
scores = np.zeros_like(data_orig_test.labels)
scores = mdl_rf.predict_proba(data_orig_test.features)[:,1].reshape(-1,1)
data_orig_test_pred.scores = scores

preds = np.zeros_like(data_orig_test.labels)
preds = mdl_rf.predict(data_orig_test.features).reshape(-1,1)
data_orig_test_pred.labels = preds

def format_probs(probs1):
    probs1 = np.array(probs1)
    probs0 = np.array(1-probs1)
    return np.concatenate((probs0, probs1), axis=1)

POST PROCESSING

In [62]:
from aif360.algorithms.postprocessing import EqOddsPostprocessing
EOPP_rf = EqOddsPostprocessing(privileged_groups = privileged_groups,
                             unprivileged_groups = unprivileged_groups,
                             seed=40)
EOPP_rf = EOPP_rf.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred_rf_eopp = EOPP_rf.predict(data_orig_test_pred)
fair_rf_eo = fair_metrics(filename, data_orig_test, data_transf_test_pred_rf_eopp, pred_is_dataset=True)
Computing fairness of the model.
In [63]:
fair_rf_eo
Out[63]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.00 1.000000 1.00000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Gender 0.71 0.827038 0.99972 -0.000274 0.013333 -0.010256 -0.027086 [0.97] 0.063411
In [64]:
from aif360.algorithms.postprocessing import CalibratedEqOddsPostprocessing
cost_constraint = "fnr"
CPP_rf = CalibratedEqOddsPostprocessing(privileged_groups = privileged_groups,
                                     unprivileged_groups = unprivileged_groups,
                                     cost_constraint=cost_constraint,
                                     seed=42)

CPP_rf = CPP_rf.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred_rf_cpp = CPP_rf.predict(data_orig_test_pred)
fair_rf_ceo = fair_metrics(filename, data_orig_test, data_transf_test_pred_rf_cpp, pred_is_dataset=True)
Computing fairness of the model.
In [65]:
fair_rf_ceo
Out[65]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.0 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Gender 0.7 0.822835 0.992853 -0.007114 0.006667 -0.016667 -0.024624 [0.9866666666666667] 0.060767
In [66]:
from aif360.algorithms.postprocessing import RejectOptionClassification
ROC_rf = RejectOptionClassification(privileged_groups = privileged_groups,
                             unprivileged_groups = unprivileged_groups)

ROC_rf = ROC_rf.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred_rf_roc = ROC_rf.predict(data_orig_test_pred)
fair_rf_roc = fair_metrics(filename, data_orig_test, data_transf_test_pred_rf_roc, pred_is_dataset=True)
print('SUCCESS: completed 1 model.')
Computing fairness of the model.
SUCCESS: completed 1 model.
In [67]:
fair_rf_roc
Out[67]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.00000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Gender 0.696667 0.76962 1.015597 0.009576 -0.033333 0.034103 0.052804 [0.7846666666666664] 0.253367

2. XGBoost Classifier

In [68]:
from xgboost import XGBClassifier
estimator = XGBClassifier(seed=40)

parameters = {
    'max_depth': range (2, 10, 2),
    'n_estimators': range(60, 240, 40),
    'learning_rate': [0.1, 0.01, 0.05]
}
grid_search = GridSearchCV(
    estimator=estimator,
    param_grid=parameters,
    scoring = 'recall',
    
    cv = 5,
    verbose=0
)

model_xg=grid_search
In [69]:
mdl_xgb = model_xg.fit(data_orig_train.features, data_orig_train.labels.ravel())
In [70]:
conf_mat_xg = confusion_matrix(data_orig_test.labels.ravel(), model_xg.predict(data_orig_test.features))
conf_mat_xg
from sklearn.metrics import accuracy_score
print(accuracy_score(data_orig_test.labels.ravel(), model_xg.predict(data_orig_test.features)))
Out[70]:
array([[  2,  88],
       [  4, 206]], dtype=int64)
0.6933333333333334

2.a. Feature importance of model

In [71]:
importances_xg = model_xg.best_estimator_.feature_importances_
indices_xg = np.argsort(importances_xg)
features = data_orig_train.feature_names
#https://stackoverflow.com/questions/48377296/get-feature-importance-from-gridsearchcv
In [72]:
importances_xg
Out[72]:
array([0.06220475, 0.10700952, 0.        , 0.10817891, 0.15189382,
       0.083426  , 0.        , 0.        , 0.04445123, 0.        ,
       0.09484909, 0.        , 0.05951812, 0.        , 0.08711442,
       0.04637603, 0.        , 0.        , 0.        , 0.11213879,
       0.        , 0.        , 0.04283933], dtype=float32)
In [73]:
importances_xg[indices_xg]
Out[73]:
array([0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.        , 0.        , 0.        , 0.        ,
       0.        , 0.04283933, 0.04445123, 0.04637603, 0.05951812,
       0.06220475, 0.083426  , 0.08711442, 0.09484909, 0.10700952,
       0.10817891, 0.11213879, 0.15189382], dtype=float32)
In [74]:
features
Out[74]:
['Gender',
 'Age',
 'Marital_Status',
 'NumMonths',
 'Savings_<500',
 'Savings_none',
 'Dependents',
 'Property_rent',
 'Job_management/self-emp/officer/highly qualif emp',
 'Debtors_guarantor',
 'Purpose_CarNew',
 'Purpose_furniture/equip',
 'CreditHistory_none/paid',
 'Purpose_CarUsed',
 'CreditAmount',
 'Collateral_real estate',
 'Debtors_none',
 'Job_unemp/unskilled-non resident',
 'Purpose_others',
 'CreditHistory_other',
 'PayBackPercent',
 'Collateral_unknown/none',
 'Purpose_education']
In [75]:
plt.figure(figsize=(20,30))
plt.title('Feature Importances')
plt.barh(range(len(indices_xg)), importances_xg[indices_xg], color='b', align='center')
plt.yticks(range(len(indices_xg)), [features[i] for i in indices_xg])
plt.xlabel('Relative Importance')
plt.show()
Out[75]:
<Figure size 1440x2160 with 0 Axes>
Out[75]:
Text(0.5, 1.0, 'Feature Importances')
Out[75]:
<BarContainer object of 23 artists>
Out[75]:
([<matplotlib.axis.YTick at 0x1e4e8980b88>,
  <matplotlib.axis.YTick at 0x1e4e8980208>,
  <matplotlib.axis.YTick at 0x1e4e899b248>,
  <matplotlib.axis.YTick at 0x1e4e8a02b88>,
  <matplotlib.axis.YTick at 0x1e4e8a03948>,
  <matplotlib.axis.YTick at 0x1e4e8a07288>,
  <matplotlib.axis.YTick at 0x1e4e8a076c8>,
  <matplotlib.axis.YTick at 0x1e4e8a07f08>,
  <matplotlib.axis.YTick at 0x1e4e8a0b408>,
  <matplotlib.axis.YTick at 0x1e4e8a07e88>,
  <matplotlib.axis.YTick at 0x1e4e8a0b608>,
  <matplotlib.axis.YTick at 0x1e4e8a110c8>,
  <matplotlib.axis.YTick at 0x1e4e8a11948>,
  <matplotlib.axis.YTick at 0x1e4e8a15208>,
  <matplotlib.axis.YTick at 0x1e4e8a15a88>,
  <matplotlib.axis.YTick at 0x1e4e8a1a348>,
  <matplotlib.axis.YTick at 0x1e4e8a1abc8>,
  <matplotlib.axis.YTick at 0x1e4e8a1d488>,
  <matplotlib.axis.YTick at 0x1e4e8a1df08>,
  <matplotlib.axis.YTick at 0x1e4e8a1a408>,
  <matplotlib.axis.YTick at 0x1e4e8a113c8>,
  <matplotlib.axis.YTick at 0x1e4e8a230c8>,
  <matplotlib.axis.YTick at 0x1e4e8a27188>],
 <a list of 23 Text yticklabel objects>)
Out[75]:
Text(0.5, 0, 'Relative Importance')

2.b. Model Explainability/interpretability

2.b.1 Using SHAP (SHapley Additive exPlanations)

In [76]:
import shap
xg_shap_values_t1 = shap.KernelExplainer(mdl_xgb.predict,data_orig_train.features)
WARNING:shap:Using 700 background data samples could cause slower run times. Consider using shap.sample(data, K) or shap.kmeans(data, K) to summarize the background as K samples.

Test data interpretation

In [78]:
xgb_explainer = shap.KernelExplainer(mdl_xgb.predict, data_orig_test.features)
xgb_shap_values = xgb_explainer.shap_values(data_orig_test.features,nsamples=10)
#https://towardsdatascience.com/explain-any-models-with-the-shap-values-use-the-kernelexplainer-79de9464897a
WARNING:shap:Using 300 background data samples could cause slower run times. Consider using shap.sample(data, K) or shap.kmeans(data, K) to summarize the background as K samples.

In [79]:
xgb_shap_values
Out[79]:
array([[0.        , 0.01166667, 0.        , ..., 0.        , 0.        ,
        0.        ],
       [0.        , 0.        , 0.        , ..., 0.        , 0.        ,
        0.        ],
       [0.        , 0.        , 0.        , ..., 0.        , 0.        ,
        0.        ],
       ...,
       [0.        , 0.        , 0.        , ..., 0.        , 0.01      ,
        0.        ],
       [0.01166667, 0.        , 0.        , ..., 0.        , 0.        ,
        0.        ],
       [0.        , 0.        , 0.        , ..., 0.        , 0.        ,
        0.        ]])
In [80]:
shap.initjs()
shap.force_plot(xgb_explainer.expected_value,xgb_shap_values[0,:], data_orig_test.features[0],data_orig_test.feature_names,link='logit')
#https://github.com/slundberg/shap
#https://github.com/slundberg/shap/issues/279
Out[80]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [81]:
shap.initjs()
shap.force_plot(xgb_explainer.expected_value,xgb_shap_values[1,:], data_orig_test.features[1],data_orig_test.feature_names,link='logit')
Out[81]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [82]:
shap.force_plot(xgb_explainer.expected_value,
                xgb_shap_values, data_orig_test.features[:,:],feature_names = data_orig_test.feature_names)
Out[82]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [83]:
p = shap.summary_plot(xgb_shap_values, data_orig_test.features, feature_names=data_orig_test.feature_names,plot_type="bar") 
display(p)
None

The variables with higher impact are the ones in the top age,gender,marital status.

In [84]:
shap.plots._waterfall.waterfall_legacy(xgb_explainer.expected_value, xgb_shap_values[0,:],feature_names=data_orig_test.feature_names)

Here credit history other and age are moving target outcome towards right i.e., 1.

Interpretation of graph: https://shap.readthedocs.io/en/latest/example_notebooks/overviews/An%20introduction%20to%20explainable%20AI%20with%20Shapley%20values.html

f(x)- model output impacted by features; E(f(x))- expected output.

One the fundemental properties of Shapley values is that they always sum up to the difference between the game outcome when all players are present and the game outcome when no players are present. For machine learning models this means that SHAP values of all the input features will always sum up to the difference between baseline (expected) model output and the current model output for the prediction being explained.

In [85]:
shap.plots._waterfall.waterfall_legacy(xgb_explainer.expected_value, xgb_shap_values[1],feature_names=data_orig_test.feature_names)

Here Credit Amount is moving the target result towards zero.

2.b.2 Using ELI5

In [86]:
#!pip install eli5
import eli5
from eli5.sklearn import PermutationImportance
In [87]:
perm_xgb = PermutationImportance(mdl_xgb).fit(data_orig_test.features, data_orig_test.labels.ravel())

Feature Importance

In [88]:
perm_imp_2=eli5.show_weights(perm_xgb,feature_names = data_orig_test.feature_names)
perm_imp_2
plt.show()
Out[88]:
Weight Feature
0.0038 ± 0.0038 Gender
0.0029 ± 0.0097 Purpose_CarNew
0 ± 0.0000 Savings_none
0 ± 0.0000 Dependents
0 ± 0.0000 Property_rent
0 ± 0.0000 Job_management/self-emp/officer/highly qualif emp
0 ± 0.0000 Debtors_guarantor
0 ± 0.0000 CreditHistory_none/paid
0 ± 0.0000 Purpose_CarUsed
0 ± 0.0000 Purpose_education
0 ± 0.0000 PayBackPercent
0 ± 0.0000 Purpose_furniture/equip
0 ± 0.0000 CreditHistory_other
0 ± 0.0000 Collateral_unknown/none
0 ± 0.0000 Purpose_others
0 ± 0.0000 Debtors_none
0 ± 0.0000 Collateral_real estate
0 ± 0.0000 Job_unemp/unskilled-non resident
0 ± 0.0000 Marital_Status
-0.0010 ± 0.0071 NumMonths
… 3 more …

2.c. Measuring fairness

Of Baseline model

In [89]:
import pandas as pd
import csv
import os
import numpy as np
import sys
from aif360.metrics import *
from sklearn.metrics import confusion_matrix, accuracy_score, f1_score, roc_curve, auc
plot_model_performance(mdl_xgb, X_test, y_test)
In [90]:
fair_xg = get_fair_metrics_and_plot(filename, data_orig_test, model_xg)
fair_xg
Computing fairness of the model.
Out[90]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.00000 1.000000 0.000000 0.000000 0.000000 0.00000 1.000000 0.000000
Gender 0.693333 0.81746 0.994982 -0.004925 0.003333 -0.010641 -0.01751 0.973333 0.070832

PRE PROCESSING

In [91]:
### Reweighing
from aif360.algorithms.preprocessing import Reweighing

RW_xg = Reweighing(unprivileged_groups=unprivileged_groups, privileged_groups=privileged_groups)

data_transf_train_xg_rw = RW_xg.fit_transform(data_orig_train)

#train and save model
xg_transf_rw = model_xg.fit(data_transf_train_xg_rw.features,
                     data_transf_train_xg_rw.labels.ravel())

data_transf_test_xg_rw = RW_xg.transform(data_orig_test)
fair_xg_rw = get_fair_metrics_and_plot(filename, data_transf_test_xg_rw, xg_transf_rw, plot=False)
Computing fairness of the model.
In [92]:
fair_xg_rw
Out[92]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000
Gender 0.684902 0.811562 0.996317 -0.003615 0.003333 -0.010641 -0.113436 0.973333 0.070832
In [93]:
from aif360.algorithms.preprocessing import DisparateImpactRemover

DIR_xg = DisparateImpactRemover()
data_transf_train_xg_dir = DIR_xg.fit_transform(data_orig_train)

# Train and save the model
xg_transf_dir = model_xg.fit(data_transf_train_xg_dir.features,data_transf_train_xg_dir.labels.ravel())
In [94]:
fair_dir_xg_dir = get_fair_metrics_and_plot(filename,data_orig_test, xg_transf_dir, plot=False)
fair_dir_xg_dir
Computing fairness of the model.
Out[94]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.00000 1.000000 0.000000 0.000000 0.000000 0.00000 1.000000 0.000000
Gender 0.693333 0.81746 0.994982 -0.004925 0.003333 -0.010641 -0.01751 0.973333 0.070832

INPROCESSING

In [95]:
#!pip install --user --upgrade tensorflow==1.15.0
#2.2.0
#!pip uninstall tensorflow
In [96]:
#!pip install "tensorflow==1.15"
#!pip install --upgrade tensorflow-hub
In [97]:
#%tensorflow_version 1.15
import tensorflow  as tf
#from tensorflow.compat.v1 import variable_scope
print('Using TensorFlow version', tf.__version__)
Using TensorFlow version 1.15.0
In [98]:
#sess = tf.compat.v1.Session()
#import tensorflow as tf

sess = tf.compat.v1.Session()
In [99]:
#import tensorflow as tf
#sess = tf.Session()
tf.compat.v1.reset_default_graph()
In [100]:
from aif360.algorithms.inprocessing.adversarial_debiasing import AdversarialDebiasing
#with tf.variable_scope('debiased_classifier',reuse=tf.AUTO_REUSE):
with tf.compat.v1.Session() as sess:
    with tf.variable_scope('scope1',reuse=tf.AUTO_REUSE) as scope:
        debiased_model_xg_ad = AdversarialDebiasing(privileged_groups = privileged_groups,
                          unprivileged_groups = unprivileged_groups,
                          scope_name=scope,
                          num_epochs=10,
                          debias=True,
                          sess=sess)
#train and save the model
        debiased_model_xg_ad.fit(data_orig_train)
        fair_xg_ad = get_fair_metrics_and_plot(filename, data_orig_test, debiased_model_xg_ad, plot=False, model_aif=True)
epoch 0; iter: 0; batch classifier loss: 1.478389; batch adversarial loss: 0.733165
epoch 1; iter: 0; batch classifier loss: 0.872163; batch adversarial loss: 0.743892
epoch 2; iter: 0; batch classifier loss: 0.854167; batch adversarial loss: 0.706732
epoch 3; iter: 0; batch classifier loss: 0.777048; batch adversarial loss: 0.706789
epoch 4; iter: 0; batch classifier loss: 0.781659; batch adversarial loss: 0.719920
epoch 5; iter: 0; batch classifier loss: 0.697654; batch adversarial loss: 0.693527
epoch 6; iter: 0; batch classifier loss: 0.704573; batch adversarial loss: 0.738594
epoch 7; iter: 0; batch classifier loss: 0.662965; batch adversarial loss: 0.720606
epoch 8; iter: 0; batch classifier loss: 0.678130; batch adversarial loss: 0.699558
epoch 9; iter: 0; batch classifier loss: 0.664784; batch adversarial loss: 0.703522
Out[100]:
<aif360.algorithms.inprocessing.adversarial_debiasing.AdversarialDebiasing at 0x1e4ea0b3088>
Computing fairness of the model.
In [101]:
fair_xg_ad
Out[101]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.00 0.000000 0.000000 1 0.0000
Gender 0.696667 0.818363 0.926263 -0.073051 -0.06 -0.082308 0.003557 [0.9706666666666667] 0.0739
In [102]:
from aif360.algorithms.inprocessing import PrejudiceRemover
debiased_model_pr_xg = PrejudiceRemover()

# Train and save the model
debiased_model_pr_xg.fit(data_orig_train)

fair_xg_pr = get_fair_metrics_and_plot(filename, data_orig_test, debiased_model_pr_xg, plot=False, model_aif=True)
fair_xg_pr
Out[102]:
<aif360.algorithms.inprocessing.prejudice_remover.PrejudiceRemover at 0x1e4ea321288>
Computing fairness of the model.
Out[102]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.00 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Gender 0.71 0.814499 0.796059 -0.186867 -0.176667 -0.196026 0.054993 [0.874] 0.118819
#
In [103]:
y_pred = debiased_model_pr_xg.predict(data_orig_test)


data_orig_test_pred = data_orig_test.copy(deepcopy=True)
In [104]:
# Prediction with the original RandomForest model
scores = np.zeros_like(data_orig_test.labels)
scores = mdl_xgb.predict_proba(data_orig_test.features)[:,1].reshape(-1,1)
data_orig_test_pred.scores = scores

preds = np.zeros_like(data_orig_test.labels)
preds = mdl_xgb.predict(data_orig_test.features).reshape(-1,1)
data_orig_test_pred.labels = preds

def format_probs(probs1):
    probs1 = np.array(probs1)
    probs0 = np.array(1-probs1)
    return np.concatenate((probs0, probs1), axis=1)

POST PROCESSING

In [105]:
from aif360.algorithms.postprocessing import EqOddsPostprocessing
EOPP_xg = EqOddsPostprocessing(privileged_groups = privileged_groups,
                             unprivileged_groups = unprivileged_groups,
                             seed=40)
EOPP_xg = EOPP_xg.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred_xg_eopp = EOPP_xg.predict(data_orig_test_pred)
fair_xg_eo = fair_metrics(filename, data_orig_test, data_transf_test_pred_xg_eopp, pred_is_dataset=True)
Computing fairness of the model.
In [106]:
fair_xg_eo
Out[106]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.00 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Gender 0.39 0.293436 1.011765 0.001915 0.003333 0.000128 0.002462 [0.7919999999999998] 0.879015
In [107]:
from aif360.algorithms.postprocessing import CalibratedEqOddsPostprocessing
cost_constraint = "fnr"
CPP_xg = CalibratedEqOddsPostprocessing(privileged_groups = privileged_groups,
                                     unprivileged_groups = unprivileged_groups,
                                     cost_constraint=cost_constraint,
                                     seed=42)

CPP_xg = CPP_xg.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred_xg_cpp = CPP_xg.predict(data_orig_test_pred)
fair_xg_ceo = fair_metrics(filename, data_orig_test, data_transf_test_pred_xg_cpp, pred_is_dataset=True)
Computing fairness of the model.
In [108]:
fair_xg_ceo
Out[108]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.00000 1.000000 0.000000 0.000000 0.000000 0.00000 1 0.000000
Gender 0.693333 0.81746 0.994982 -0.004925 0.003333 -0.010641 -0.01751 [0.9733333333333334] 0.070832
In [109]:
from aif360.algorithms.postprocessing import RejectOptionClassification
ROC_xg = RejectOptionClassification(privileged_groups = privileged_groups,
                             unprivileged_groups = unprivileged_groups)

ROC_xg = ROC_xg.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred_xg_roc = ROC_xg.predict(data_orig_test_pred)
fair_xg_roc = fair_metrics(filename, data_orig_test, data_transf_test_pred_xg_roc, pred_is_dataset=True)
print('SUCCESS: completed 1 model.')
Computing fairness of the model.
SUCCESS: completed 1 model.
In [110]:
fair_xg_roc
Out[110]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.00 1.00000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Gender 0.67 0.75188 0.985727 -0.009029 -0.043333 0.010641 0.048427 [0.8226666666666664] 0.266354
In [ ]:
 
In [ ]:
 

3. XGBOOST with out hyper-parameter tuning

In [111]:
from xgboost import XGBClassifier
model_xgb2 = XGBClassifier(seed=40)
In [112]:
mdl_xgb2 = model_xgb2.fit(data_orig_train.features, data_orig_train.labels.ravel())
In [113]:
conf_mat_xg2 = confusion_matrix(data_orig_test.labels.ravel(), model_xgb2.predict(data_orig_test.features))
conf_mat_xg2
from sklearn.metrics import accuracy_score
print(accuracy_score(data_orig_test.labels.ravel(), model_xgb2.predict(data_orig_test.features)))
Out[113]:
array([[ 32,  58],
       [ 36, 174]], dtype=int64)
0.6866666666666666

3.a. Feature importance of model

In [114]:
importances_xg2 = model_xgb2.feature_importances_
indices_xg2 = np.argsort(importances_xg2)
features2 = data_orig_train.feature_names
#https://stackoverflow.com/questions/48377296/get-feature-importance-from-gridsearchcv
In [115]:
importances_xg2
Out[115]:
array([0.02361056, 0.07240672, 0.02394954, 0.04656706, 0.08524894,
       0.02640945, 0.03555013, 0.04276526, 0.03081001, 0.05483867,
       0.0554199 , 0.03113955, 0.04219624, 0.09255903, 0.02998231,
       0.04272536, 0.02681142, 0.02949912, 0.        , 0.08695158,
       0.0307787 , 0.0283696 , 0.06141086], dtype=float32)
In [116]:
importances_xg2[indices_xg2]
Out[116]:
array([0.        , 0.02361056, 0.02394954, 0.02640945, 0.02681142,
       0.0283696 , 0.02949912, 0.02998231, 0.0307787 , 0.03081001,
       0.03113955, 0.03555013, 0.04219624, 0.04272536, 0.04276526,
       0.04656706, 0.05483867, 0.0554199 , 0.06141086, 0.07240672,
       0.08524894, 0.08695158, 0.09255903], dtype=float32)
In [117]:
features2
Out[117]:
['Gender',
 'Age',
 'Marital_Status',
 'NumMonths',
 'Savings_<500',
 'Savings_none',
 'Dependents',
 'Property_rent',
 'Job_management/self-emp/officer/highly qualif emp',
 'Debtors_guarantor',
 'Purpose_CarNew',
 'Purpose_furniture/equip',
 'CreditHistory_none/paid',
 'Purpose_CarUsed',
 'CreditAmount',
 'Collateral_real estate',
 'Debtors_none',
 'Job_unemp/unskilled-non resident',
 'Purpose_others',
 'CreditHistory_other',
 'PayBackPercent',
 'Collateral_unknown/none',
 'Purpose_education']
In [118]:
plt.figure(figsize=(20,30))
plt.title('Feature Importances')
plt.barh(range(len(indices_xg2)), importances_xg2[indices_xg2], color='b', align='center')
plt.yticks(range(len(indices_xg2)), [features2[i] for i in indices_xg2])
plt.xlabel('Relative Importance')
plt.show()
Out[118]:
<Figure size 1440x2160 with 0 Axes>
Out[118]:
Text(0.5, 1.0, 'Feature Importances')
Out[118]:
<BarContainer object of 23 artists>
Out[118]:
([<matplotlib.axis.YTick at 0x1e4ed272988>,
  <matplotlib.axis.YTick at 0x1e4ed26ef48>,
  <matplotlib.axis.YTick at 0x1e4ed253748>,
  <matplotlib.axis.YTick at 0x1e4ed2cb408>,
  <matplotlib.axis.YTick at 0x1e4ed2ce288>,
  <matplotlib.axis.YTick at 0x1e4ed2ceac8>,
  <matplotlib.axis.YTick at 0x1e4ed2d3408>,
  <matplotlib.axis.YTick at 0x1e4ed2d3cc8>,
  <matplotlib.axis.YTick at 0x1e4ed2ce788>,
  <matplotlib.axis.YTick at 0x1e4ed2d7388>,
  <matplotlib.axis.YTick at 0x1e4ed2d7c08>,
  <matplotlib.axis.YTick at 0x1e4ed2db808>,
  <matplotlib.axis.YTick at 0x1e4ed2df308>,
  <matplotlib.axis.YTick at 0x1e4ed2dfdc8>,
  <matplotlib.axis.YTick at 0x1e4ed2e38c8>,
  <matplotlib.axis.YTick at 0x1e4ed2e6348>,
  <matplotlib.axis.YTick at 0x1e4ed2e6bc8>,
  <matplotlib.axis.YTick at 0x1e4ed2eb848>,
  <matplotlib.axis.YTick at 0x1e4ed2e3f88>,
  <matplotlib.axis.YTick at 0x1e4ed2eb3c8>,
  <matplotlib.axis.YTick at 0x1e4ed2ef4c8>,
  <matplotlib.axis.YTick at 0x1e4ed2f2188>,
  <matplotlib.axis.YTick at 0x1e4ed2f2988>],
 <a list of 23 Text yticklabel objects>)
Out[118]:
Text(0.5, 0, 'Relative Importance')

3.b. Model Explainability/interpretability

3.b.1 Using SHAP (SHapley Additive exPlanations)

In [119]:
import shap
xg_shap_values_t = shap.KernelExplainer(mdl_xgb2.predict,data_orig_train.features)
WARNING:shap:Using 700 background data samples could cause slower run times. Consider using shap.sample(data, K) or shap.kmeans(data, K) to summarize the background as K samples.

Test data interpretation

In [120]:
xgb_explainer2 = shap.KernelExplainer(mdl_xgb2.predict, data_orig_test.features)
xgb_shap_values2 = xgb_explainer2.shap_values(data_orig_test.features,nsamples=10)
#https://towardsdatascience.com/explain-any-models-with-the-shap-values-use-the-kernelexplainer-79de9464897a
WARNING:shap:Using 300 background data samples could cause slower run times. Consider using shap.sample(data, K) or shap.kmeans(data, K) to summarize the background as K samples.

In [121]:
xgb_shap_values2
Out[121]:
array([[ 0.        ,  0.        ,  0.        , ...,  0.        ,
         0.        ,  0.        ],
       [ 0.        ,  0.        ,  0.        , ...,  0.        ,
         0.        ,  0.        ],
       [ 0.        ,  0.        ,  0.        , ...,  0.035     ,
         0.        ,  0.        ],
       ...,
       [ 0.15666667,  0.        ,  0.        , ...,  0.        ,
         0.        ,  0.        ],
       [ 0.        ,  0.06333333,  0.        , ...,  0.        ,
         0.        ,  0.        ],
       [ 0.        , -0.61166667,  0.        , ...,  0.        ,
         0.        ,  0.        ]])
In [122]:
shap.initjs()
shap.force_plot(xgb_explainer2.expected_value,xgb_shap_values2[0,:],  data_orig_test.features[0],data_orig_test.feature_names,link='logit')
#https://github.com/slundberg/shap
#https://github.com/slundberg/shap/issues/279
Out[122]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.

The field age is pushing target outcome towards lower value and collateral unknown/none, purpose others are pushing the target towards higher value which resulted in the final probability of occurrance as .73

In [123]:
shap.initjs()
shap.force_plot(xgb_explainer2.expected_value,xgb_shap_values2[1,:],  data_orig_test.features[1],data_orig_test.feature_names,link='logit')
Out[123]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.

Here only credit amount has impact in moving target outcome towards lower value than the base value which is the mean value to target outcome

In [124]:
data_orig_test.feature_names
Out[124]:
['Gender',
 'Age',
 'Marital_Status',
 'NumMonths',
 'Savings_<500',
 'Savings_none',
 'Dependents',
 'Property_rent',
 'Job_management/self-emp/officer/highly qualif emp',
 'Debtors_guarantor',
 'Purpose_CarNew',
 'Purpose_furniture/equip',
 'CreditHistory_none/paid',
 'Purpose_CarUsed',
 'CreditAmount',
 'Collateral_real estate',
 'Debtors_none',
 'Job_unemp/unskilled-non resident',
 'Purpose_others',
 'CreditHistory_other',
 'PayBackPercent',
 'Collateral_unknown/none',
 'Purpose_education']
In [125]:
shap.force_plot(xgb_explainer2.expected_value,
                xgb_shap_values2, data_orig_test.features[:,:],feature_names = data_orig_test.feature_names)
Out[125]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [126]:
p = shap.summary_plot(xgb_shap_values2, data_orig_test.features, feature_names=data_orig_test.feature_names,plot_type="bar") 
display(p)
None

variables with higher impact are CreditAmount,NumMonths,Savings.

In [127]:
shap.plots._waterfall.waterfall_legacy(xgb_explainer2.expected_value, xgb_shap_values2[0,:],feature_names=data_orig_test.feature_names)

Here purpose other and collateral unknown/none are pushing target to higher value and age is pushing it towards lower value.

Interpretation of graph: https://shap.readthedocs.io/en/latest/example_notebooks/overviews/An%20introduction%20to%20explainable%20AI%20with%20Shapley%20values.html

  • f(x)- model output impacted by features; E(f(x))- expected output.

  • One the fundemental properties of Shapley values is that they always sum up to the difference between the game outcome when all players are present and the game outcome when no players are present. For machine learning models this means that SHAP values of all the input features will always sum up to the difference between baseline (expected) model output and the current model output for the prediction being explained.

In [128]:
shap.plots._waterfall.waterfall_legacy(xgb_explainer2.expected_value, xgb_shap_values2[1],feature_names=data_orig_test.feature_names)

3.b.2 Using ELI5

In [129]:
#!pip install eli5
import eli5
from eli5.sklearn import PermutationImportance
In [130]:
perm_xgb2 = PermutationImportance(mdl_xgb2).fit(data_orig_test.features, data_orig_test.labels.ravel())

Feature Importance

In [131]:
perm_imp_3=eli5.show_weights(perm_xgb2,feature_names = data_orig_test.feature_names)
perm_imp_3
plt.show()
Out[131]:
Weight Feature
0.0347 ± 0.0417 CreditAmount
0.0187 ± 0.0341 NumMonths
0.0120 ± 0.0374 Savings_<500
0.0093 ± 0.0115 Job_management/self-emp/officer/highly qualif emp
0.0067 ± 0.0223 Purpose_CarNew
0.0033 ± 0.0119 Property_rent
0.0027 ± 0.0078 Savings_none
0.0020 ± 0.0108 Gender
0.0007 ± 0.0078 Job_unemp/unskilled-non resident
0.0007 ± 0.0050 Dependents
0.0007 ± 0.0088 Debtors_none
0 ± 0.0000 Purpose_others
-0.0007 ± 0.0107 Collateral_unknown/none
-0.0013 ± 0.0033 Debtors_guarantor
-0.0013 ± 0.0241 CreditHistory_other
-0.0020 ± 0.0068 Purpose_education
-0.0033 ± 0.0193 CreditHistory_none/paid
-0.0040 ± 0.0142 Collateral_real estate
-0.0040 ± 0.0281 PayBackPercent
-0.0067 ± 0.0103 Purpose_furniture/equip
… 3 more …

Explaining individual predictions

In [132]:
from eli5 import show_prediction
show_prediction(mdl_xgb2, data_orig_test.features[1], show_feature_values=True,feature_names = data_orig_test.feature_names)
Out[132]:

y=0.0 (probability 0.867, score -1.871) top features

Contribution? Feature Value
+2.091 NumMonths 60.000
+0.394 Collateral_unknown/none 1.000
+0.370 Savings_<500 1.000
+0.320 PayBackPercent 3.000
+0.229 Job_management/self-emp/officer/highly qualif emp 0.000
+0.208 CreditHistory_other 0.000
+0.152 Purpose_CarUsed 0.000
+0.134 Savings_none 0.000
+0.115 Collateral_real estate 0.000
+0.044 CreditAmount 0.362
+0.022 Debtors_guarantor 0.000
-0.006 Purpose_education 0.000
-0.014 Debtors_none 1.000
-0.015 Job_unemp/unskilled-non resident 0.000
-0.068 Dependents 1.000
-0.073 CreditHistory_none/paid 0.000
-0.095 Age 1.000
-0.137 Property_rent 0.000
-0.141 Purpose_furniture/equip 0.000
-0.171 Purpose_CarNew 0.000
-0.200 Gender 1.000
-0.279 Marital_Status 1.000
-1.011 <BIAS> 1.000
In [ ]:
 

3.c. Measuring fairness

Of Baseline model

In [133]:
import pandas as pd
import csv
import os
import numpy as np
import sys
from aif360.metrics import *
from sklearn.metrics import confusion_matrix, accuracy_score, f1_score, roc_curve, auc
plot_model_performance(mdl_xgb2, X_test, y_test)
In [134]:
fair_xg2 = get_fair_metrics_and_plot(filename, data_orig_test, mdl_xgb2)
fair_xg2
Computing fairness of the model.
Out[134]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.00000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000
Gender 0.686667 0.78733 0.748932 -0.209029 -0.156667 -0.247564 0.006019 0.789333 0.178936

PRE PROCESSING

In [135]:
### Reweighing
from aif360.algorithms.preprocessing import Reweighing

RW_xg2 = Reweighing(unprivileged_groups=unprivileged_groups, privileged_groups=privileged_groups)

data_transf_train_xg2_rw = RW_xg2.fit_transform(data_orig_train)

#train and save model
xg2_transf_rw = model_xgb2.fit(data_transf_train_xg2_rw.features,
                     data_transf_train_xg2_rw.labels.ravel())

data_transf_test_xg2_rw = RW_xg2.transform(data_orig_test)
fair_xg2_rw = get_fair_metrics_and_plot(filename, data_transf_test_xg2_rw, xg2_transf_rw, plot=False)
Computing fairness of the model.
In [136]:
fair_xg2_rw
Out[136]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.00000 1.000000 1.00000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000
Gender 0.67248 0.776646 0.77779 -0.183884 -0.156667 -0.247564 -0.024238 0.789333 0.178936
In [137]:
from aif360.algorithms.preprocessing import DisparateImpactRemover

DIR_xg2 = DisparateImpactRemover()
data_transf_train_xg2_dir = DIR_xg2.fit_transform(data_orig_train)

# Train and save the model
xg2_transf_dir = model_xgb2.fit(data_transf_train_xg2_dir.features,data_transf_train_xg2_dir.labels.ravel())
In [138]:
fair_dir_xg2_dir = get_fair_metrics_and_plot(filename,data_orig_test, xg2_transf_dir, plot=False)
fair_dir_xg2_dir
Computing fairness of the model.
Out[138]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.00 0.000000 0.000000 1.000000 0.000000
Gender 0.683333 0.785553 0.763063 -0.197264 -0.18 -0.211538 0.050616 0.773333 0.179354

INPROCESSING

In [139]:
#!pip install --user --upgrade tensorflow==1.15.0
#2.2.0
#!pip uninstall tensorflow
In [140]:
#!pip install "tensorflow==1.15"
#!pip install --upgrade tensorflow-hub
In [141]:
#%tensorflow_version 1.15
import tensorflow  as tf
#from tensorflow.compat.v1 import variable_scope
print('Using TensorFlow version', tf.__version__)
Using TensorFlow version 1.15.0
In [142]:
#sess = tf.compat.v1.Session()
#import tensorflow as tf

sess = tf.compat.v1.Session()
In [143]:
#import tensorflow as tf
#sess = tf.Session()
tf.compat.v1.reset_default_graph()
In [144]:
from aif360.algorithms.inprocessing.adversarial_debiasing import AdversarialDebiasing
#with tf.variable_scope('debiased_classifier',reuse=tf.AUTO_REUSE):
with tf.compat.v1.Session() as sess:
    with tf.variable_scope('scope1',reuse=tf.AUTO_REUSE) as scope:
        debiased_model_xg2_ad = AdversarialDebiasing(privileged_groups = privileged_groups,
                          unprivileged_groups = unprivileged_groups,
                          scope_name=scope,
                          num_epochs=10,
                          debias=True,
                          sess=sess)
#train and save the model
        debiased_model_xg2_ad.fit(data_orig_train)
        fair_xg2_ad = get_fair_metrics_and_plot(filename, data_orig_test, debiased_model_xg2_ad, plot=False, model_aif=True)
epoch 0; iter: 0; batch classifier loss: 0.919759; batch adversarial loss: 0.727894
epoch 1; iter: 0; batch classifier loss: 1.033717; batch adversarial loss: 0.747176
epoch 2; iter: 0; batch classifier loss: 0.759145; batch adversarial loss: 0.764973
epoch 3; iter: 0; batch classifier loss: 0.830653; batch adversarial loss: 0.740471
epoch 4; iter: 0; batch classifier loss: 0.751414; batch adversarial loss: 0.788038
epoch 5; iter: 0; batch classifier loss: 0.776110; batch adversarial loss: 0.778579
epoch 6; iter: 0; batch classifier loss: 0.772419; batch adversarial loss: 0.792659
epoch 7; iter: 0; batch classifier loss: 0.681601; batch adversarial loss: 0.784802
epoch 8; iter: 0; batch classifier loss: 0.808873; batch adversarial loss: 0.779869
epoch 9; iter: 0; batch classifier loss: 0.684466; batch adversarial loss: 0.813438
Out[144]:
<aif360.algorithms.inprocessing.adversarial_debiasing.AdversarialDebiasing at 0x1e4ede6d748>
Computing fairness of the model.
In [145]:
fair_xg2_ad
Out[145]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.00 0.000000 0.000000 1 0.000000
Gender 0.703333 0.823762 0.973764 -0.025992 -0.01 -0.037308 -0.019973 [0.98] 0.063806
In [146]:
from aif360.algorithms.inprocessing import PrejudiceRemover
debiased_model_pr_xg2 = PrejudiceRemover()

# Train and save the model
debiased_model_pr_xg2.fit(data_orig_train)

fair_xg2_pr = get_fair_metrics_and_plot(filename, data_orig_test, debiased_model_pr_xg2, plot=False, model_aif=True)
fair_xg2_pr
Out[146]:
<aif360.algorithms.inprocessing.prejudice_remover.PrejudiceRemover at 0x1e4ef991888>
Computing fairness of the model.
Out[146]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.00 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Gender 0.71 0.814499 0.796059 -0.186867 -0.176667 -0.196026 0.054993 [0.874] 0.118819
#
In [147]:
y_pred = debiased_model_pr_xg2.predict(data_orig_test)


data_orig_test_pred = data_orig_test.copy(deepcopy=True)
In [148]:
# Prediction with the original RandomForest model
scores = np.zeros_like(data_orig_test.labels)
scores = mdl_xgb2.predict_proba(data_orig_test.features)[:,1].reshape(-1,1)
data_orig_test_pred.scores = scores

preds = np.zeros_like(data_orig_test.labels)
preds = mdl_xgb2.predict(data_orig_test.features).reshape(-1,1)
data_orig_test_pred.labels = preds

def format_probs(probs1):
    probs1 = np.array(probs1)
    probs0 = np.array(1-probs1)
    return np.concatenate((probs0, probs1), axis=1)

POST PROCESSING

In [149]:
from aif360.algorithms.postprocessing import EqOddsPostprocessing
EOPP_xg2 = EqOddsPostprocessing(privileged_groups = privileged_groups,
                             unprivileged_groups = unprivileged_groups,
                             seed=40)
EOPP_xg2 = EOPP_xg2.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred_xg2_eopp = EOPP_xg2.predict(data_orig_test_pred)
fair_xg2_eo = fair_metrics(filename, data_orig_test, data_transf_test_pred_xg2_eopp, pred_is_dataset=True)
Computing fairness of the model.
In [150]:
fair_xg2_eo
Out[150]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.00000 0.000000 0.000000 0.00000 1 0.000000
Gender 0.673333 0.776256 0.987088 -0.00985 -0.013333 -0.009744 0.00383 [0.7253333333333329] 0.194577
In [151]:
from aif360.algorithms.postprocessing import CalibratedEqOddsPostprocessing
cost_constraint = "fnr"
CPP_xg2 = CalibratedEqOddsPostprocessing(privileged_groups = privileged_groups,
                                     unprivileged_groups = unprivileged_groups,
                                     cost_constraint=cost_constraint,
                                     seed=42)

CPP_xg2 = CPP_xg2.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred_xg2_cpp = CPP_xg2.predict(data_orig_test_pred)
fair_xg2_ceo = fair_metrics(filename, data_orig_test, data_transf_test_pred_xg2_cpp, pred_is_dataset=True)
Computing fairness of the model.
In [152]:
fair_xg2_ceo
Out[152]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.00000 1.000000 0.000000 0.0 0.00 0.000000 1 0.000000
Gender 0.683333 0.80167 0.635294 -0.364706 -0.3 -0.41 0.050616 [0.904] 0.117799
In [153]:
from aif360.algorithms.postprocessing import RejectOptionClassification
ROC_xg2 = RejectOptionClassification(privileged_groups = privileged_groups,
                             unprivileged_groups = unprivileged_groups)

ROC_xg2 = ROC_xg2.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred_xg2_roc = ROC_xg2.predict(data_orig_test_pred)
fair_xg2_roc = fair_metrics(filename, data_orig_test, data_transf_test_pred_xg2_roc, pred_is_dataset=True)
print('SUCCESS: completed 1 model.')
Computing fairness of the model.
SUCCESS: completed 1 model.
In [154]:
fair_xg2_roc
Out[154]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.00 1.000000 1.000000 0.000000 0.00 0.000000 0.000000 1 0.000000
Gender 0.66 0.715084 0.936819 -0.031737 -0.06 -0.017692 0.050889 [0.686666666666666] 0.348007
In [ ]:
 

4. RANDOM FOREST CLASSIFIER MODEL WITH OUT HYPER-PARAMETER TUNING

In [155]:
#Creating the classifier
rf_model2 = RandomForestClassifier(random_state=40)
model_rf2=rf_model2
In [156]:
mdl_rf2 = model_rf2.fit(data_orig_train.features, data_orig_train.labels.ravel())
In [157]:
from sklearn.metrics import confusion_matrix
conf_mat_rf2 = confusion_matrix(data_orig_test.labels.ravel(), model_rf2.predict(data_orig_test.features))
conf_mat_rf2
from sklearn.metrics import accuracy_score
print(accuracy_score(data_orig_test.labels.ravel(), model_rf2.predict(data_orig_test.features)))
Out[157]:
array([[ 30,  60],
       [ 23, 187]], dtype=int64)
0.7233333333333334
In [158]:
unique, counts = np.unique(data_orig_test.labels.ravel(), return_counts=True)
dict(zip(unique, counts))
Out[158]:
{0.0: 90, 1.0: 210}

4.a. Model Explainability/interpretability

4.a.1 Using SHAP (SHapley Additive exPlanations)

In [159]:
import shap
rf_shap_values_t2 = shap.KernelExplainer(mdl_rf2.predict,data_orig_train.features)
WARNING:shap:Using 700 background data samples could cause slower run times. Consider using shap.sample(data, K) or shap.kmeans(data, K) to summarize the background as K samples.

Test data interpretation

In [160]:
rf_explainer2 = shap.KernelExplainer(mdl_rf2.predict, data_orig_test.features)
rf_shap_values2 = rf_explainer2.shap_values(data_orig_test.features,nsamples=10)
#https://towardsdatascience.com/explain-any-models-with-the-shap-values-use-the-kernelexplainer-79de9464897a
WARNING:shap:Using 300 background data samples could cause slower run times. Consider using shap.sample(data, K) or shap.kmeans(data, K) to summarize the background as K samples.

In [161]:
rf_shap_values2
Out[161]:
array([[ 0.        ,  0.        ,  0.        , ...,  0.        ,
         0.        ,  0.        ],
       [ 0.        ,  0.        ,  0.        , ...,  0.        ,
         0.        ,  0.        ],
       [ 0.        ,  0.        ,  0.        , ...,  0.02      ,
         0.        ,  0.        ],
       ...,
       [ 0.        ,  0.        ,  0.055     , ...,  0.        ,
         0.        ,  0.        ],
       [ 0.        ,  0.        ,  0.        , ...,  0.        ,
         0.08833333,  0.        ],
       [ 0.        , -0.51333333,  0.        , ...,  0.        ,
         0.        ,  0.        ]])
In [162]:
rf_explainer2.expected_value
rf_shap_values2
Out[162]:
0.8233333333333334
Out[162]:
array([[ 0.        ,  0.        ,  0.        , ...,  0.        ,
         0.        ,  0.        ],
       [ 0.        ,  0.        ,  0.        , ...,  0.        ,
         0.        ,  0.        ],
       [ 0.        ,  0.        ,  0.        , ...,  0.02      ,
         0.        ,  0.        ],
       ...,
       [ 0.        ,  0.        ,  0.055     , ...,  0.        ,
         0.        ,  0.        ],
       [ 0.        ,  0.        ,  0.        , ...,  0.        ,
         0.08833333,  0.        ],
       [ 0.        , -0.51333333,  0.        , ...,  0.        ,
         0.        ,  0.        ]])
In [163]:
shap.initjs()
shap.force_plot(rf_explainer2.expected_value,rf_shap_values2[0,:],  data_orig_test.features[0],data_orig_test.feature_names,link='logit')
#https://github.com/slundberg/shap
#https://github.com/slundberg/shap/issues/279
Out[163]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [164]:
shap.initjs()
shap.force_plot(rf_explainer2.expected_value,rf_shap_values2[1,:], data_orig_test.features[1],data_orig_test.feature_names,link='logit')
Out[164]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [165]:
shap.initjs()
shap.force_plot(rf_explainer2.expected_value,rf_shap_values2[2,:], data_orig_test.features[2],data_orig_test.feature_names,link='logit')
Out[165]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [166]:
data_orig_test.feature_names
Out[166]:
['Gender',
 'Age',
 'Marital_Status',
 'NumMonths',
 'Savings_<500',
 'Savings_none',
 'Dependents',
 'Property_rent',
 'Job_management/self-emp/officer/highly qualif emp',
 'Debtors_guarantor',
 'Purpose_CarNew',
 'Purpose_furniture/equip',
 'CreditHistory_none/paid',
 'Purpose_CarUsed',
 'CreditAmount',
 'Collateral_real estate',
 'Debtors_none',
 'Job_unemp/unskilled-non resident',
 'Purpose_others',
 'CreditHistory_other',
 'PayBackPercent',
 'Collateral_unknown/none',
 'Purpose_education']
In [167]:
shap.force_plot(rf_explainer2.expected_value,
                rf_shap_values2, data_orig_test.features[:,:],feature_names = data_orig_test.feature_names)
Out[167]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [168]:
p = shap.summary_plot(rf_shap_values2, data_orig_test.features, feature_names=data_orig_test.feature_names,plot_type="bar") 
display(p)
None

Variables with higher impact are displayed at the top such as gender,age,nummonths etc

In [169]:
shap.plots._waterfall.waterfall_legacy(rf_explainer2.expected_value, rf_shap_values2[0,:],feature_names=data_orig_test.feature_names)

Interpretation of graph: https://shap.readthedocs.io/en/latest/example_notebooks/overviews/An%20introduction%20to%20explainable%20AI%20with%20Shapley%20values.html

f(x)- model output impacted by features; E(f(x))- expected output.

One the fundemental properties of Shapley values is that they always sum up to the difference between the game outcome when all players are present and the game outcome when no players are present. For machine learning models this means that SHAP values of all the input features will always sum up to the difference between baseline (expected) model output and the current model output for the prediction being explained.

In [170]:
shap.plots._waterfall.waterfall_legacy(rf_explainer2.expected_value, rf_shap_values2[1],feature_names=data_orig_test.feature_names)

4.a.2 Using ELI5

In [171]:
#!pip install eli5
import eli5
from eli5.sklearn import PermutationImportance
In [172]:
perm_rf2 = PermutationImportance(mdl_rf2).fit(data_orig_test.features, data_orig_test.labels.ravel())
In [173]:
data_orig_test.labels[:10,:].ravel()
Out[173]:
array([0., 0., 1., 1., 1., 0., 1., 1., 1., 1.])

Feature Importance

In [174]:
perm_imp_11=eli5.show_weights(perm_rf2,feature_names = data_orig_test.feature_names)
perm_imp_11
plt.show()
Out[174]:
Weight Feature
0.0267 ± 0.0163 Gender
0.0167 ± 0.0042 Job_management/self-emp/officer/highly qualif emp
0.0147 ± 0.0417 CreditAmount
0.0127 ± 0.0154 NumMonths
0.0100 ± 0.0169 Marital_Status
0.0080 ± 0.0116 CreditHistory_other
0.0060 ± 0.0065 Purpose_furniture/equip
0.0047 ± 0.0196 Age
0.0033 ± 0.0042 Debtors_guarantor
0.0027 ± 0.0265 Savings_<500
0.0020 ± 0.0124 Purpose_CarNew
0.0007 ± 0.0027 Job_unemp/unskilled-non resident
0.0000 ± 0.0094 Purpose_CarUsed
0 ± 0.0000 Purpose_others
-0.0007 ± 0.0050 Debtors_none
-0.0020 ± 0.0100 Savings_none
-0.0033 ± 0.0140 CreditHistory_none/paid
-0.0040 ± 0.0165 PayBackPercent
-0.0047 ± 0.0068 Property_rent
-0.0047 ± 0.0033 Dependents
… 3 more …

Explaining individual predictions

In [175]:
show_prediction(mdl_rf2, data_orig_test.features[0], show_feature_values=True,feature_names = data_orig_test.feature_names)
Out[175]:

y=1.0 (probability 0.650) top features

Contribution? Feature Value
+0.703 <BIAS> 1.000
+0.025 Age 1.000
+0.018 Job_management/self-emp/officer/highly qualif emp 0.000
+0.017 Purpose_CarNew 0.000
+0.016 Property_rent 0.000
+0.015 Dependents 1.000
+0.015 CreditAmount 0.043
+0.011 Collateral_real estate 1.000
+0.010 Purpose_education 0.000
+0.010 Purpose_furniture/equip 0.000
+0.010 Gender 1.000
+0.008 Collateral_unknown/none 0.000
+0.004 Job_unemp/unskilled-non resident 0.000
+0.002 Debtors_none 1.000
+0.000 Purpose_others 0.000
-0.010 Purpose_CarUsed 0.000
-0.010 Debtors_guarantor 0.000
-0.011 Marital_Status 0.000
-0.012 CreditHistory_none/paid 0.000
-0.012 Savings_none 0.000
-0.013 PayBackPercent 4.000
-0.037 Savings_<500 1.000
-0.049 NumMonths 24.000
-0.061 CreditHistory_other 0.000
In [176]:
from eli5 import show_prediction
show_prediction(mdl_rf2, data_orig_test.features[1], show_feature_values=True,feature_names = data_orig_test.feature_names)
Out[176]:

y=0.0 (probability 0.640) top features

Contribution? Feature Value
+0.297 <BIAS> 1.000
+0.202 NumMonths 60.000
+0.074 CreditAmount 0.362
+0.067 Collateral_unknown/none 1.000
+0.055 Savings_<500 1.000
+0.038 CreditHistory_other 0.000
+0.033 Savings_none 0.000
+0.023 Purpose_CarUsed 0.000
+0.007 PayBackPercent 3.000
+0.006 Collateral_real estate 0.000
+0.006 Job_management/self-emp/officer/highly qualif emp 0.000
+0.003 Debtors_guarantor 0.000
+0.002 Marital_Status 1.000
-0.000 Job_unemp/unskilled-non resident 0.000
-0.001 Debtors_none 1.000
-0.002 Purpose_others 0.000
-0.004 Purpose_furniture/equip 0.000
-0.008 Purpose_education 0.000
-0.008 Property_rent 0.000
-0.010 Dependents 1.000
-0.011 Purpose_CarNew 0.000
-0.020 Gender 1.000
-0.038 Age 1.000
-0.073 CreditHistory_none/paid 0.000

4.b. Measuring fairness

Of Baseline model

In [177]:
import pandas as pd
import csv
import os
import numpy as np
import sys
from aif360.metrics import *
from sklearn.metrics import confusion_matrix, accuracy_score, f1_score, roc_curve, auc
plot_model_performance(mdl_rf2, X_test, y_test)
In [178]:
fair = get_fair_metrics_and_plot(filename, data_orig_test, mdl_rf2)
fair
Computing fairness of the model.
Out[178]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000
Gender 0.723333 0.818381 0.776222 -0.196717 -0.173333 -0.215897 0.040766 0.847333 0.130518
In [179]:
type(data_orig_train)
Out[179]:
aif360.datasets.binary_label_dataset.BinaryLabelDataset

PRE PROCESSING

In [180]:
### Reweighing
from aif360.algorithms.preprocessing import Reweighing

RW_rf2 = Reweighing(unprivileged_groups=unprivileged_groups, privileged_groups=privileged_groups)

data_transf_train_rf2_rw = RW_rf2.fit_transform(data_orig_train)

#train and save model
rf2_transf_rw = model_rf2.fit(data_transf_train_rf2_rw.features,
                     data_transf_train_rf2_rw.labels.ravel())

data_transf_test_rf2_rw = RW_rf2.transform(data_orig_test)
fair_rf2_rw = get_fair_metrics_and_plot(filename, data_transf_test_rf2_rw, rf2_transf_rw, plot=False)
Computing fairness of the model.
In [181]:
fair_rf_rw
Out[181]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.00000 0.000000 0.0 0.000000 0.000000 1.000000 0.000000
Gender 0.701725 0.822616 1.00124 0.001227 0.0 -0.004615 -0.107827 0.987333 0.057005
In [182]:
from aif360.algorithms.preprocessing import DisparateImpactRemover

DIR_rf2 = DisparateImpactRemover()
data_transf_train_rf2_dir = DIR_rf2.fit_transform(data_orig_train)

# Train and save the model
rf2_transf_dir = model_rf2.fit(data_transf_train_rf2_dir.features,data_transf_train_rf2_dir.labels.ravel())
In [183]:
fair_dir_rf2_dir = get_fair_metrics_and_plot(filename,data_orig_test, rf2_transf_dir, plot=False)
fair_dir_rf2_dir
Computing fairness of the model.
Out[183]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000
Gender 0.743333 0.833693 0.874531 -0.109713 -0.096667 -0.122179 0.019425 0.849333 0.108552
In [184]:
conf_mat_rf2_dir = confusion_matrix(data_orig_test.labels.ravel(), rf2_transf_dir.predict(data_orig_test.features))
conf_mat_rf2_dir
from sklearn.metrics import accuracy_score
print(accuracy_score(data_orig_test.labels.ravel(), rf2_transf_dir.predict(data_orig_test.features)))
Out[184]:
array([[ 30,  60],
       [ 17, 193]], dtype=int64)
0.7433333333333333

INPROCESSING

In [185]:
#!pip install --user --upgrade tensorflow==1.15.0
#2.2.0
#!pip uninstall tensorflow
In [186]:
#!pip install "tensorflow==1.15"
#!pip install --upgrade tensorflow-hub
In [187]:
#%tensorflow_version 1.15
import tensorflow  as tf
#from tensorflow.compat.v1 import variable_scope
print('Using TensorFlow version', tf.__version__)
Using TensorFlow version 1.15.0
In [188]:
#sess = tf.compat.v1.Session()
#import tensorflow as tf

sess = tf.compat.v1.Session()
In [189]:
#import tensorflow as tf
#sess = tf.Session()
tf.compat.v1.reset_default_graph()
In [190]:
from aif360.algorithms.inprocessing.adversarial_debiasing import AdversarialDebiasing
#with tf.variable_scope('debiased_classifier',reuse=tf.AUTO_REUSE):
with tf.compat.v1.Session() as sess:
    with tf.variable_scope('scope1',reuse=tf.AUTO_REUSE) as scope:
        debiased_model_rf2_ad = AdversarialDebiasing(privileged_groups = privileged_groups,
                          unprivileged_groups = unprivileged_groups,
                          scope_name=scope,
                          num_epochs=10,
                          debias=True,
                          sess=sess)
#train and save the model
        debiased_model_rf2_ad.fit(data_orig_train)
        fair_rf2_ad = get_fair_metrics_and_plot(filename, data_orig_test, debiased_model_rf2_ad, plot=False, model_aif=True)
epoch 0; iter: 0; batch classifier loss: 0.919759; batch adversarial loss: 0.727894
epoch 1; iter: 0; batch classifier loss: 1.033717; batch adversarial loss: 0.747176
epoch 2; iter: 0; batch classifier loss: 0.759145; batch adversarial loss: 0.764973
epoch 3; iter: 0; batch classifier loss: 0.830653; batch adversarial loss: 0.740471
epoch 4; iter: 0; batch classifier loss: 0.751414; batch adversarial loss: 0.788038
epoch 5; iter: 0; batch classifier loss: 0.776110; batch adversarial loss: 0.778579
epoch 6; iter: 0; batch classifier loss: 0.772419; batch adversarial loss: 0.792659
epoch 7; iter: 0; batch classifier loss: 0.681601; batch adversarial loss: 0.784802
epoch 8; iter: 0; batch classifier loss: 0.808873; batch adversarial loss: 0.779869
epoch 9; iter: 0; batch classifier loss: 0.684466; batch adversarial loss: 0.813438
Out[190]:
<aif360.algorithms.inprocessing.adversarial_debiasing.AdversarialDebiasing at 0x1e4f2697cc8>
Computing fairness of the model.
In [191]:
fair_rf2_ad
Out[191]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.00 0.000000 0.000000 1 0.000000
Gender 0.703333 0.823762 0.973764 -0.025992 -0.01 -0.037308 -0.019973 [0.98] 0.063806
In [192]:
from aif360.algorithms.inprocessing import PrejudiceRemover
debiased_model_pr_rf2 = PrejudiceRemover()

# Train and save the model
debiased_model_pr_rf2.fit(data_orig_train)

fair_rf2_pr = get_fair_metrics_and_plot(filename, data_orig_test, debiased_model_pr_rf2, plot=False, model_aif=True)
fair_rf2_pr
Out[192]:
<aif360.algorithms.inprocessing.prejudice_remover.PrejudiceRemover at 0x1e4f272b388>
Computing fairness of the model.
Out[192]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.00 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Gender 0.71 0.814499 0.796059 -0.186867 -0.176667 -0.196026 0.054993 [0.874] 0.118819
#
In [193]:
y_pred = debiased_model_pr_rf2.predict(data_orig_test)


data_orig_test_pred = data_orig_test.copy(deepcopy=True)
In [194]:
# Prediction with the original RandomForest model
scores = np.zeros_like(data_orig_test.labels)
scores = mdl_rf2.predict_proba(data_orig_test.features)[:,1].reshape(-1,1)
data_orig_test_pred.scores = scores

preds = np.zeros_like(data_orig_test.labels)
preds = mdl_rf2.predict(data_orig_test.features).reshape(-1,1)
data_orig_test_pred.labels = preds

def format_probs(probs1):
    probs1 = np.array(probs1)
    probs0 = np.array(1-probs1)
    return np.concatenate((probs0, probs1), axis=1)

POST PROCESSING

In [195]:
from aif360.algorithms.postprocessing import EqOddsPostprocessing
EOPP_rf2 = EqOddsPostprocessing(privileged_groups = privileged_groups,
                             unprivileged_groups = unprivileged_groups,
                             seed=40)
EOPP_rf2 = EOPP_rf2.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred_rf2_eopp = EOPP_rf2.predict(data_orig_test_pred)
fair_rf2_eo = fair_metrics(filename, data_orig_test, data_transf_test_pred_rf2_eopp, pred_is_dataset=True)
Computing fairness of the model.
In [196]:
fair_rf2_eo
Out[196]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.00 1.000000 1.000000 0.00000 0.00 0.000000 0.000000 1 0.000000
Gender 0.71 0.808791 0.991644 -0.00684 0.01 -0.021154 -0.027086 [0.8206666666666667] 0.142082
In [197]:
from aif360.algorithms.postprocessing import CalibratedEqOddsPostprocessing
cost_constraint = "fnr"
CPP_rf2 = CalibratedEqOddsPostprocessing(privileged_groups = privileged_groups,
                                     unprivileged_groups = unprivileged_groups,
                                     cost_constraint=cost_constraint,
                                     seed=42)

CPP_rf2 = CPP_rf2.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred_rf2_cpp = CPP_rf2.predict(data_orig_test_pred)
fair_rf2_ceo = fair_metrics(filename, data_orig_test, data_transf_test_pred_rf2_cpp, pred_is_dataset=True)
Computing fairness of the model.
In [198]:
fair_rf2_ceo
Out[198]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.00 0.000 0.000000 1 0.000000
Gender 0.706667 0.820408 0.764706 -0.235294 -0.15 -0.295 -0.031737 [0.932] 0.086272
In [199]:
from aif360.algorithms.postprocessing import RejectOptionClassification
ROC_rf2 = RejectOptionClassification(privileged_groups = privileged_groups,
                             unprivileged_groups = unprivileged_groups)

ROC_rf2 = ROC_rf2.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred_rf2_roc = ROC_rf2.predict(data_orig_test_pred)
fair_rf2_roc = fair_metrics(filename, data_orig_test, data_transf_test_pred_rf2_roc, pred_is_dataset=True)
print('SUCCESS: completed 1 model.')
Computing fairness of the model.
SUCCESS: completed 1 model.
In [200]:
fair_rf2_roc
Out[200]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Gender 0.626667 0.670588 1.006325 0.002736 -0.013333 0.008718 0.020793 [0.7166666666666663] 0.410976

5. KNN

In [201]:
from sklearn import neighbors
n_neighbors = 15
knn = neighbors.KNeighborsClassifier(n_neighbors, weights='distance')
In [202]:
knn.fit(data_orig_train.features, data_orig_train.labels.ravel())
Out[202]:
KNeighborsClassifier(n_neighbors=15, weights='distance')
In [203]:
conf_mat_knn = confusion_matrix(data_orig_test.labels.ravel(), knn.predict(data_orig_test.features))
conf_mat_knn
from sklearn.metrics import accuracy_score
print(accuracy_score(data_orig_test.labels.ravel(), knn.predict(data_orig_test.features)))
Out[203]:
array([[ 21,  69],
       [ 15, 195]], dtype=int64)
0.72

5.a. Model Explainability/interpretability

5.a.1 Using SHAP (SHapley Additive exPlanations)

In [204]:
knn_explainer = shap.KernelExplainer(knn.predict, data_orig_test.features)
knn_shap_values = knn_explainer.shap_values(data_orig_test.features,nsamples=10)
WARNING:shap:Using 300 background data samples could cause slower run times. Consider using shap.sample(data, K) or shap.kmeans(data, K) to summarize the background as K samples.

In [205]:
#shap.dependence_plot(0, knn_shap_values, data_orig_test.features)
In [206]:
# plot the SHAP values for the 0th observation 
shap.force_plot(knn_explainer.expected_value,knn_shap_values[0,:],  data_orig_test.features[0],data_orig_test.feature_names,link='logit') 
Out[206]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [207]:
# plot the SHAP values for the 1st observation 
shap.force_plot(knn_explainer.expected_value,knn_shap_values[1,:],  data_orig_test.features[1],data_orig_test.feature_names,link='logit') 
Out[207]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [208]:
shap.force_plot(knn_explainer.expected_value, knn_shap_values,  data_orig_test.feature_names,link='logit')
Out[208]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [209]:
shap.summary_plot(knn_shap_values, data_orig_test.features,feature_names=data_orig_test.feature_names, plot_type="violin")

Feature Importance

perm_imp_11=eli5.show_weights(knn,feature_names = data_orig_test.feature_names) perm_imp_11 plt.show()

Explaining individual predictions

In [210]:
from eli5 import show_prediction
show_prediction(knn, data_orig_test.features[1], show_feature_values=True,feature_names = data_orig_test.feature_names)
Out[210]:
Error: estimator KNeighborsClassifier(n_neighbors=15, weights='distance') is not supported

5.b. Measuring fairness

Of Baseline model

In [211]:
import pandas as pd
import csv
import os
import numpy as np
import sys
from aif360.metrics import *
from sklearn.metrics import confusion_matrix, accuracy_score, f1_score, roc_curve, auc
plot_model_performance(knn, X_test, y_test)
In [212]:
fair = get_fair_metrics_and_plot(filename, data_orig_test, knn)
fair
Computing fairness of the model.
Out[212]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.00 1.000000 1.000000 0.000000 0.00 0.000 0.000000 1.000000 0.000000
Gender 0.72 0.822785 0.877551 -0.111628 -0.11 -0.115 0.036115 0.900667 0.104695

PRE PROCESSING

In [213]:
### Reweighing
from aif360.algorithms.preprocessing import Reweighing

RW_knn = Reweighing(unprivileged_groups=unprivileged_groups, privileged_groups=privileged_groups)

data_transf_train_knn = RW_knn.fit_transform(data_orig_train)

# Train and save the model
knn_transf_rw = knn.fit(data_transf_train_knn.features,
                     data_transf_train_knn.labels.ravel())

data_transf_test_knn_rw = RW_knn.transform(data_orig_test)
fair_knn_rw = get_fair_metrics_and_plot(filename, data_transf_test_knn_rw, knn_transf_rw, plot=False)
Computing fairness of the model.
In [214]:
fair_knn_rw
Out[214]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.00000 1.000000 0.000000 0.00 0.000 0.000000 1.000000 0.000000
Gender 0.709289 0.81484 0.895261 -0.094858 -0.11 -0.115 -0.025923 0.900667 0.104695
In [215]:
from aif360.algorithms.preprocessing import DisparateImpactRemover

DIR = DisparateImpactRemover()
data_transf_train_knn_dir = DIR.fit_transform(data_orig_train)
# Train and save the model
knn_transf_dir = knn.fit(data_transf_train_knn_dir.features,
                     data_transf_train_knn_dir.labels.ravel())
In [216]:
fair_knn_dir = get_fair_metrics_and_plot(filename, data_orig_test, knn_transf_dir, plot=False)
fair_knn_dir
Computing fairness of the model.
Out[216]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000
Gender 0.703333 0.811839 0.935355 -0.057729 -0.043333 -0.069359 -0.003557 0.913333 0.116144

INPROCESSING

In [217]:
#!pip install tensorflow
import tensorflow  as tf
#from tensorflow.compat.v1 import variable_scope
print('Using TensorFlow version', tf.__version__)
Using TensorFlow version 1.15.0
In [218]:
#sess = tf.compat.v1.Session()
#import tensorflow as tf

sess = tf.compat.v1.Session()
In [219]:
#import tensorflow as tf
#sess = tf.Session()
tf.compat.v1.reset_default_graph()
In [220]:
from aif360.algorithms.inprocessing.adversarial_debiasing import AdversarialDebiasing
#with tf.variable_scope('debiased_classifier',reuse=tf.AUTO_REUSE):
with tf.compat.v1.Session() as sess:
    with tf.variable_scope('scope4',reuse=tf.AUTO_REUSE) as scope:
        debiased_model_knn_ad = AdversarialDebiasing(privileged_groups = privileged_groups,
                          unprivileged_groups = unprivileged_groups,
                          scope_name=scope,
                          num_epochs=10,
                          debias=True,
                          sess=sess)
        debiased_model_knn_ad.fit(data_orig_train)
        fair_knn_ad = get_fair_metrics_and_plot(filename, data_orig_test, debiased_model_knn_ad, plot=False, model_aif=True)
epoch 0; iter: 0; batch classifier loss: 2.830437; batch adversarial loss: 0.696634
epoch 1; iter: 0; batch classifier loss: 2.144904; batch adversarial loss: 0.695833
epoch 2; iter: 0; batch classifier loss: 1.969160; batch adversarial loss: 0.700665
epoch 3; iter: 0; batch classifier loss: 1.742991; batch adversarial loss: 0.707673
epoch 4; iter: 0; batch classifier loss: 1.422695; batch adversarial loss: 0.706706
epoch 5; iter: 0; batch classifier loss: 1.205362; batch adversarial loss: 0.730006
epoch 6; iter: 0; batch classifier loss: 1.030017; batch adversarial loss: 0.751804
epoch 7; iter: 0; batch classifier loss: 1.068889; batch adversarial loss: 0.783473
epoch 8; iter: 0; batch classifier loss: 0.966531; batch adversarial loss: 0.727006
epoch 9; iter: 0; batch classifier loss: 0.816778; batch adversarial loss: 0.796189
Out[220]:
<aif360.algorithms.inprocessing.adversarial_debiasing.AdversarialDebiasing at 0x1e4f4d19088>
Computing fairness of the model.
In [221]:
fair_knn_ad
Out[221]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.00000 1 0.000000
Gender 0.506667 0.635468 1.897059 0.467305 0.516667 0.435256 -0.26156 [0.8500000000000001] 0.372552
In [222]:
from aif360.algorithms.inprocessing import PrejudiceRemover
debiased_model_knn_pr = PrejudiceRemover()

# Train and save the model
debiased_model_knn_pr.fit(data_orig_train)

fair_knn_pr = get_fair_metrics_and_plot(filename, data_orig_test, debiased_model_knn_pr, plot=False, model_aif=True)
fair_knn_pr
Out[222]:
<aif360.algorithms.inprocessing.prejudice_remover.PrejudiceRemover at 0x1e4f506b308>
Computing fairness of the model.
Out[222]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.00 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Gender 0.71 0.814499 0.796059 -0.186867 -0.176667 -0.196026 0.054993 [0.874] 0.118819
#
In [223]:
y_pred = debiased_model_knn_pr.predict(data_orig_test)

data_orig_test_pred = data_orig_test.copy(deepcopy=True)
In [224]:
# Prediction with the original RandomForest model
scores = np.zeros_like(data_orig_test.labels)
scores = knn.predict_proba(data_orig_test.features)[:,1].reshape(-1,1)
data_orig_test_pred.scores = scores

preds = np.zeros_like(data_orig_test.labels)
preds = knn.predict(data_orig_test.features).reshape(-1,1)
data_orig_test_pred.labels = preds

def format_probs(probs1):
    probs1 = np.array(probs1)
    probs0 = np.array(1-probs1)
    return np.concatenate((probs0, probs1), axis=1)

POST PROCESSING

In [225]:
from aif360.algorithms.postprocessing import EqOddsPostprocessing
EOPP_knn = EqOddsPostprocessing(privileged_groups = privileged_groups,
                             unprivileged_groups = unprivileged_groups,
                             seed=40)
EOPP_knn = EOPP_knn.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred_knn_eop = EOPP_knn.predict(data_orig_test_pred)
fair_knn_eo = fair_metrics(filename, data_orig_test, data_transf_test_pred_knn_eop, pred_is_dataset=True)
Computing fairness of the model.
In [226]:
fair_knn_eo
Out[226]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.0 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.00000
Gender 0.7 0.810924 0.993225 -0.006019 -0.003333 -0.009359 -0.008208 [0.9173333333333333] 0.11312
In [227]:
from aif360.algorithms.postprocessing import CalibratedEqOddsPostprocessing
cost_constraint = "fnr"
CPP_knn = CalibratedEqOddsPostprocessing(privileged_groups = privileged_groups,
                                     unprivileged_groups = unprivileged_groups,
                                     cost_constraint=cost_constraint,
                                     seed=40)

CPP_knn = CPP_knn.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred_knn_cp = CPP_knn.predict(data_orig_test_pred)
fair_knn_ceo = fair_metrics(filename, data_orig_test, data_transf_test_pred_knn_cp, pred_is_dataset=True)
Computing fairness of the model.
In [228]:
fair_knn_ceo
Out[228]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.0 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.00000
Gender 0.7 0.818548 0.835294 -0.164706 -0.116667 -0.198333 -0.008208 [0.9446666666666667] 0.08021
In [229]:
from aif360.algorithms.postprocessing import RejectOptionClassification
ROC_knn = RejectOptionClassification(privileged_groups = privileged_groups,
                             unprivileged_groups = unprivileged_groups)

ROC_knn = ROC_knn.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred_knn_roc = ROC_knn.predict(data_orig_test_pred) 
fair_knn_roc = fair_metrics(filename, data_orig_test, data_transf_test_pred_knn_roc, pred_is_dataset=True)
print('SUCCESS: completed 1 model.')
Computing fairness of the model.
SUCCESS: completed 1 model.
In [230]:
fair_knn_roc
Out[230]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.00 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Gender 0.67 0.744186 1.052235 0.030369 -0.003333 0.049103 0.032011 [0.7993333333333332] 0.287874

6. Logistic Regression

In [231]:
from sklearn.linear_model import LogisticRegression

lr = LogisticRegression()
In [232]:
lr.fit(data_orig_train.features, data_orig_train.labels.ravel())
Out[232]:
LogisticRegression()
In [233]:
conf_mat_lr = confusion_matrix(data_orig_test.labels.ravel(), lr.predict(data_orig_test.features))
conf_mat_lr
from sklearn.metrics import accuracy_score
print(accuracy_score(data_orig_test.labels.ravel(), lr.predict(data_orig_test.features)))
Out[233]:
array([[ 22,  68],
       [ 20, 190]], dtype=int64)
0.7066666666666667

6.a. Model Explainability/interpretability

6.a.1 Using SHAP (SHapley Additive exPlanations)

In [234]:
lr_explainer = shap.KernelExplainer(lr.predict, data_orig_test.features)
lr_shap_values = lr_explainer.shap_values(data_orig_test.features,nsamples=10)
WARNING:shap:Using 300 background data samples could cause slower run times. Consider using shap.sample(data, K) or shap.kmeans(data, K) to summarize the background as K samples.

In [235]:
# plot the SHAP values for the 0th observation 
shap.force_plot(lr_explainer.expected_value,lr_shap_values[0,:],  data_orig_test.features[0],data_orig_test.feature_names,link='logit') 
Out[235]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [236]:
# plot the SHAP values for the 1st observation 
shap.force_plot(lr_explainer.expected_value,lr_shap_values[1,:],  data_orig_test.features[1],data_orig_test.feature_names,link='logit') 
Out[236]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [237]:
shap.force_plot(lr_explainer.expected_value, lr_shap_values,  data_orig_test.feature_names,link='logit')
Out[237]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [238]:
shap.summary_plot(lr_shap_values, data_orig_test.features,feature_names=data_orig_test.feature_names, plot_type="violin")

Feature Importance

perm_imp_11=eli5.show_weights(knn,feature_names = data_orig_test.feature_names) perm_imp_11 plt.show()

Explaining individual predictions

In [239]:
from eli5 import show_prediction
show_prediction(lr, data_orig_test.features[1], show_feature_values=True,feature_names = data_orig_test.feature_names)
Out[239]:

y=0.0 (probability 0.615, score -0.467) top features

Contribution? Feature Value
+2.376 NumMonths 60.000
+0.887 Savings_<500 1.000
+0.722 PayBackPercent 3.000
+0.256 CreditAmount 0.362
+0.199 Dependents 1.000
+0.168 Collateral_unknown/none 1.000
-0.183 Gender 1.000
-0.250 Debtors_none 1.000
-0.389 Marital_Status 1.000
-0.804 Age 1.000
-2.516 <BIAS> 1.000

6.b. Measuring fairness

Of Baseline model

In [240]:
import pandas as pd
import csv
import os
import numpy as np
import sys
from aif360.metrics import *
from sklearn.metrics import confusion_matrix, accuracy_score, f1_score, roc_curve, auc
plot_model_performance(lr, X_test, y_test)
In [241]:
fair = get_fair_metrics_and_plot(filename, data_orig_test, lr)
fair
Computing fairness of the model.
Out[241]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.00 0.000000 0.000000 1.000000 0.000000
Gender 0.706667 0.811966 0.783219 -0.198632 -0.17 -0.220385 0.033926 0.883333 0.122465

PRE PROCESSING

In [242]:
### Reweighing
from aif360.algorithms.preprocessing import Reweighing

RW_lr = Reweighing(unprivileged_groups=unprivileged_groups, privileged_groups=privileged_groups)

data_transf_train_lr = RW_lr.fit_transform(data_orig_train)

# Train and save the model
lr_transf_rw = lr.fit(data_transf_train_knn.features,
                     data_transf_train_knn.labels.ravel())

data_transf_test_lr_rw = RW_lr.transform(data_orig_test)
fair_lr_rw = get_fair_metrics_and_plot(filename, data_transf_test_lr_rw, lr_transf_rw, plot=False)
Computing fairness of the model.
In [243]:
fair_lr_rw
Out[243]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.00000 1.000000 1.000000 0.000000 0.00 0.000000 0.000000 1.000000 0.000000
Gender 0.69192 0.801457 0.802706 -0.179873 -0.17 -0.220385 -0.017159 0.883333 0.122465
In [244]:
from aif360.algorithms.preprocessing import DisparateImpactRemover

DIR = DisparateImpactRemover()
data_transf_train_lr_dir = DIR.fit_transform(data_orig_train)
# Train and save the model
lr_transf_dir = lr.fit(data_transf_train_lr_dir.features,
                     data_transf_train_lr_dir.labels.ravel())
In [245]:
fair_lr_dir = get_fair_metrics_and_plot(filename, data_orig_test, lr_transf_dir, plot=False)
fair_lr_dir
Computing fairness of the model.
Out[245]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.0 1.000000 1.000000 0.0000 0.00 0.000000 0.000000 1.000 0.000000
Gender 0.7 0.807692 0.817195 -0.1658 -0.14 -0.185385 0.024624 0.878 0.126449

INPROCESSING

In [246]:
#!pip install tensorflow
import tensorflow  as tf
#from tensorflow.compat.v1 import variable_scope
print('Using TensorFlow version', tf.__version__)
Using TensorFlow version 1.15.0
In [247]:
#sess = tf.compat.v1.Session()
#import tensorflow as tf

sess = tf.compat.v1.Session()
In [248]:
#import tensorflow as tf
#sess = tf.Session()
tf.compat.v1.reset_default_graph()
In [249]:
from aif360.algorithms.inprocessing.adversarial_debiasing import AdversarialDebiasing
#with tf.variable_scope('debiased_classifier',reuse=tf.AUTO_REUSE):
with tf.compat.v1.Session() as sess:
    with tf.variable_scope('scope5',reuse=tf.AUTO_REUSE) as scope:
        debiased_model_lr_ad = AdversarialDebiasing(privileged_groups = privileged_groups,
                          unprivileged_groups = unprivileged_groups,
                          scope_name=scope,
                          num_epochs=10,
                          debias=True,
                          sess=sess)
        debiased_model_lr_ad.fit(data_orig_train)
        fair_lr_ad = get_fair_metrics_and_plot(filename, data_orig_test, debiased_model_lr_ad, plot=False, model_aif=True)
epoch 0; iter: 0; batch classifier loss: 1.772720; batch adversarial loss: 0.759462
epoch 1; iter: 0; batch classifier loss: 1.368406; batch adversarial loss: 0.740671
epoch 2; iter: 0; batch classifier loss: 0.904510; batch adversarial loss: 0.678539
epoch 3; iter: 0; batch classifier loss: 0.766160; batch adversarial loss: 0.705621
epoch 4; iter: 0; batch classifier loss: 0.845030; batch adversarial loss: 0.713136
epoch 5; iter: 0; batch classifier loss: 0.763155; batch adversarial loss: 0.707767
epoch 6; iter: 0; batch classifier loss: 0.724550; batch adversarial loss: 0.704370
epoch 7; iter: 0; batch classifier loss: 0.768791; batch adversarial loss: 0.712196
epoch 8; iter: 0; batch classifier loss: 0.665683; batch adversarial loss: 0.724559
epoch 9; iter: 0; batch classifier loss: 0.746345; batch adversarial loss: 0.681918
Out[249]:
<aif360.algorithms.inprocessing.adversarial_debiasing.AdversarialDebiasing at 0x1e4f9176a48>
Computing fairness of the model.
In [250]:
fair_lr_ad
Out[250]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.0000 0.000000 0.000000 0.00000 1 0.000000
Gender 0.706667 0.822581 1.051835 0.0487 0.016667 0.069872 0.01751 [0.98] 0.076523
In [251]:
from aif360.algorithms.inprocessing import PrejudiceRemover
debiased_model_lr_pr = PrejudiceRemover()

# Train and save the model
debiased_model_lr_pr.fit(data_orig_train)

fair_lr_pr = get_fair_metrics_and_plot(filename, data_orig_test, debiased_model_lr_pr, plot=False, model_aif=True)
fair_lr_pr
Out[251]:
<aif360.algorithms.inprocessing.prejudice_remover.PrejudiceRemover at 0x1e4f90fce88>
Computing fairness of the model.
Out[251]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.00 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Gender 0.71 0.814499 0.796059 -0.186867 -0.176667 -0.196026 0.054993 [0.874] 0.118819
#
In [252]:
y_pred = debiased_model_lr_pr.predict(data_orig_test)

data_orig_test_pred = data_orig_test.copy(deepcopy=True)
In [253]:
# Prediction with the original RandomForest model
scores = np.zeros_like(data_orig_test.labels)
scores = lr.predict_proba(data_orig_test.features)[:,1].reshape(-1,1)
data_orig_test_pred.scores = scores

preds = np.zeros_like(data_orig_test.labels)
preds = lr.predict(data_orig_test.features).reshape(-1,1)
data_orig_test_pred.labels = preds

def format_probs(probs1):
    probs1 = np.array(probs1)
    probs0 = np.array(1-probs1)
    return np.concatenate((probs0, probs1), axis=1)

POST PROCESSING

In [254]:
from aif360.algorithms.postprocessing import EqOddsPostprocessing
EOPP_lr = EqOddsPostprocessing(privileged_groups = privileged_groups,
                             unprivileged_groups = unprivileged_groups,
                             seed=40)
EOPP_lr = EOPP_lr.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred_lr_eop = EOPP_lr.predict(data_orig_test_pred)
fair_lr_eo = fair_metrics(filename, data_orig_test, data_transf_test_pred_lr_eop, pred_is_dataset=True)
Computing fairness of the model.
In [255]:
fair_lr_eo
Out[255]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.00 1.000000 1.000000 0.000000 0.000000 0.000000 0.00000 1 0.000000
Gender 0.69 0.801706 0.992726 -0.006293 -0.016667 -0.000641 0.01067 [0.8713333333333333] 0.130736
In [256]:
from aif360.algorithms.postprocessing import CalibratedEqOddsPostprocessing
cost_constraint = "fnr"
CPP_lr = CalibratedEqOddsPostprocessing(privileged_groups = privileged_groups,
                                     unprivileged_groups = unprivileged_groups,
                                     cost_constraint=cost_constraint,
                                     seed=40)

CPP_lr = CPP_lr.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred_lr_cp = CPP_lr.predict(data_orig_test_pred)
fair_lr_ceo = fair_metrics(filename, data_orig_test, data_transf_test_pred_lr_cp, pred_is_dataset=True)
Computing fairness of the model.
In [257]:
fair_lr_ceo
Out[257]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.0 0.0 0.000000 1 0.000000
Gender 0.693333 0.811475 0.741176 -0.258824 -0.2 -0.3 0.015321 [0.9266666666666667] 0.097068
In [258]:
from aif360.algorithms.postprocessing import RejectOptionClassification
ROC_lr = RejectOptionClassification(privileged_groups = privileged_groups,
                             unprivileged_groups = unprivileged_groups)

ROC_lr = ROC_lr.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred_lr_roc = ROC_lr.predict(data_orig_test_pred) 
fair_lr_roc = fair_metrics(filename, data_orig_test, data_transf_test_pred_lr_roc, pred_is_dataset=True)
print('SUCCESS: completed 1 model.')
Computing fairness of the model.
SUCCESS: completed 1 model.
In [259]:
fair_lr_roc
Out[259]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.00000 1 0.000000
Gender 0.716667 0.770889 0.921735 -0.042955 -0.066667 -0.033333 0.04788 [0.7699999999999998] 0.277745
In [ ]:
 

7. SVM

In [260]:
from sklearn.svm import SVC
#gs = grid_search_cv.best_estimator_
svm = SVC(C=0.85, break_ties=False, cache_size=200, class_weight=None, coef0=0.0,
    decision_function_shape='ovr', degree=3, gamma='scale', kernel='linear',
    max_iter=-1, random_state=42, shrinking=True, tol=0.001, probability=True,
    verbose=False)
svm.fit(data_orig_train.features, data_orig_train.labels.ravel())
Out[260]:
SVC(C=0.85, kernel='linear', probability=True, random_state=42)
In [261]:
conf_mat_svm = confusion_matrix(data_orig_test.labels.ravel(), svm.predict(data_orig_test.features))
conf_mat_svm
from sklearn.metrics import accuracy_score
print(accuracy_score(data_orig_test.labels.ravel(), svm.predict(data_orig_test.features)))
Out[261]:
array([[ 16,  74],
       [ 12, 198]], dtype=int64)
0.7133333333333334

7.a. Model Explainability/interpretability

7.a.1 Using SHAP (SHapley Additive exPlanations)

In [262]:
svm_explainer = shap.KernelExplainer(svm.predict, data_orig_test.features)
svm_shap_values = svm_explainer.shap_values(data_orig_test.features,nsamples=10)
WARNING:shap:Using 300 background data samples could cause slower run times. Consider using shap.sample(data, K) or shap.kmeans(data, K) to summarize the background as K samples.

In [263]:
# plot the SHAP values for the 0th observation 
shap.force_plot(svm_explainer.expected_value,svm_shap_values[0,:],  data_orig_test.features[0],data_orig_test.feature_names,link='logit') 
Out[263]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [264]:
# plot the SHAP values for the 1st observation 
shap.force_plot(svm_explainer.expected_value,svm_shap_values[1,:],  data_orig_test.features[1],data_orig_test.feature_names,link='logit') 
Out[264]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [265]:
shap.force_plot(svm_explainer.expected_value, svm_shap_values,  data_orig_test.feature_names,link='logit')
Out[265]:
Visualization omitted, Javascript library not loaded!
Have you run `initjs()` in this notebook? If this notebook was from another user you must also trust this notebook (File -> Trust notebook). If you are viewing this notebook on github the Javascript has been stripped for security. If you are using JupyterLab this error is because a JupyterLab extension has not yet been written.
In [266]:
shap.summary_plot(svm_shap_values, data_orig_test.features,feature_names=data_orig_test.feature_names, plot_type="violin")

Feature Importance

perm_imp_11=eli5.show_weights(knn,feature_names = data_orig_test.feature_names) perm_imp_11 plt.show()

Explaining individual predictions

In [267]:
from eli5 import show_prediction
show_prediction(svm, data_orig_test.features[1], show_feature_values=True,feature_names = data_orig_test.feature_names)
Out[267]:

y=0.0 (probability 0.572, score -0.390) top features

Contribution? Feature Value
+1.970 NumMonths 60.000
+0.618 Savings_<500 1.000
+0.531 PayBackPercent 3.000
+0.389 CreditAmount 0.362
+0.136 Dependents 1.000
+0.096 Collateral_unknown/none 1.000
-0.151 Debtors_none 1.000
-0.187 Marital_Status 1.000
-0.199 Gender 1.000
-0.531 Age 1.000
-2.283 <BIAS> 1.000

7.b. Measuring fairness

Of Baseline model

In [268]:
import pandas as pd
import csv
import os
import numpy as np
import sys
from aif360.metrics import *
from sklearn.metrics import confusion_matrix, accuracy_score, f1_score, roc_curve, auc
plot_model_performance(svm, X_test, y_test)
In [269]:
fair_svm = get_fair_metrics_and_plot(filename, data_orig_test, svm)
fair_svm
Computing fairness of the model.
Out[269]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.00 0.000000
Gender 0.713333 0.821577 0.859751 -0.132421 -0.083333 -0.167821 -0.022435 0.91 0.095524

PRE PROCESSING

In [270]:
### Reweighing
from aif360.algorithms.preprocessing import Reweighing

RW_svm = Reweighing(unprivileged_groups=unprivileged_groups, privileged_groups=privileged_groups)

data_transf_train_svm = RW_svm.fit_transform(data_orig_train)

# Train and save the model
svm_transf_rw = svm.fit(data_transf_train_knn.features,
                     data_transf_train_knn.labels.ravel())

data_transf_test_svm_rw = RW_svm.transform(data_orig_test)
fair_svm_rw = get_fair_metrics_and_plot(filename, data_transf_test_svm_rw, svm_transf_rw, plot=False)
Computing fairness of the model.
In [271]:
fair_svm_rw
Out[271]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.00000 1.0000 0.000000 0.000000 0.000000 0.000000 1.00 0.000000
Gender 0.700079 0.81258 0.8787 -0.114193 -0.083333 -0.167821 -0.087746 0.91 0.095524
In [272]:
from aif360.algorithms.preprocessing import DisparateImpactRemover

DIR = DisparateImpactRemover()
data_transf_train_svm_dir = DIR.fit_transform(data_orig_train)
# Train and save the model
svm_transf_dir = svm.fit(data_transf_train_svm_dir.features,
                     data_transf_train_svm_dir.labels.ravel())
In [273]:
fair_svm_dir = get_fair_metrics_and_plot(filename, data_orig_test, svm_transf_dir, plot=False)
fair_svm_dir
Computing fairness of the model.
Out[273]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.0000 0.000000 0.000000 0.000000 1.000000 0.000000
Gender 0.696667 0.810021 0.838963 -0.1513 -0.103333 -0.185513 -0.012859 0.903333 0.110076

INPROCESSING

In [274]:
#!pip install tensorflow
import tensorflow  as tf
#from tensorflow.compat.v1 import variable_scope
print('Using TensorFlow version', tf.__version__)
Using TensorFlow version 1.15.0
In [275]:
#sess = tf.compat.v1.Session()
#import tensorflow as tf

sess = tf.compat.v1.Session()
In [276]:
#import tensorflow as tf
#sess = tf.Session()
tf.compat.v1.reset_default_graph()
In [277]:
from aif360.algorithms.inprocessing.adversarial_debiasing import AdversarialDebiasing
#with tf.variable_scope('debiased_classifier',reuse=tf.AUTO_REUSE):
with tf.compat.v1.Session() as sess:
    with tf.variable_scope('scope6',reuse=tf.AUTO_REUSE) as scope:
        debiased_model_svm_ad = AdversarialDebiasing(privileged_groups = privileged_groups,
                          unprivileged_groups = unprivileged_groups,
                          scope_name=scope,
                          num_epochs=10,
                          debias=True,
                          sess=sess)
        debiased_model_svm_ad.fit(data_orig_train)
        fair_svm_ad = get_fair_metrics_and_plot(filename, data_orig_test, debiased_model_svm_ad, plot=False, model_aif=True)
epoch 0; iter: 0; batch classifier loss: 1.772720; batch adversarial loss: 0.759462
epoch 1; iter: 0; batch classifier loss: 1.368406; batch adversarial loss: 0.740671
epoch 2; iter: 0; batch classifier loss: 0.904510; batch adversarial loss: 0.678539
epoch 3; iter: 0; batch classifier loss: 0.766160; batch adversarial loss: 0.705621
epoch 4; iter: 0; batch classifier loss: 0.845030; batch adversarial loss: 0.713136
epoch 5; iter: 0; batch classifier loss: 0.763155; batch adversarial loss: 0.707767
epoch 6; iter: 0; batch classifier loss: 0.724550; batch adversarial loss: 0.704370
epoch 7; iter: 0; batch classifier loss: 0.768791; batch adversarial loss: 0.712196
epoch 8; iter: 0; batch classifier loss: 0.665683; batch adversarial loss: 0.724559
epoch 9; iter: 0; batch classifier loss: 0.746345; batch adversarial loss: 0.681918
Out[277]:
<aif360.algorithms.inprocessing.adversarial_debiasing.AdversarialDebiasing at 0x1e4fbe3c4c8>
Computing fairness of the model.
In [278]:
fair_svm_ad
Out[278]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.0000 0.000000 0.000000 0.00000 1 0.000000
Gender 0.706667 0.822581 1.051835 0.0487 0.016667 0.069872 0.01751 [0.98] 0.076523
In [279]:
from aif360.algorithms.inprocessing import PrejudiceRemover
debiased_model_svm_pr = PrejudiceRemover()

# Train and save the model
debiased_model_svm_pr.fit(data_orig_train)

fair_svm_pr = get_fair_metrics_and_plot(filename, data_orig_test, debiased_model_svm_pr, plot=False, model_aif=True)
fair_svm_pr
Out[279]:
<aif360.algorithms.inprocessing.prejudice_remover.PrejudiceRemover at 0x1e4fbf2ac48>
Computing fairness of the model.
Out[279]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.00 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Gender 0.71 0.814499 0.796059 -0.186867 -0.176667 -0.196026 0.054993 [0.874] 0.118819
#
In [280]:
y_pred = debiased_model_svm_pr.predict(data_orig_test)

data_orig_test_pred = data_orig_test.copy(deepcopy=True)
In [281]:
# Prediction with the original RandomForest model
scores = np.zeros_like(data_orig_test.labels)
scores = svm.predict_proba(data_orig_test.features)[:,1].reshape(-1,1)
data_orig_test_pred.scores = scores

preds = np.zeros_like(data_orig_test.labels)
preds = svm.predict(data_orig_test.features).reshape(-1,1)
data_orig_test_pred.labels = preds

def format_probs(probs1):
    probs1 = np.array(probs1)
    probs0 = np.array(1-probs1)
    return np.concatenate((probs0, probs1), axis=1)

POST PROCESSING

In [282]:
from aif360.algorithms.postprocessing import EqOddsPostprocessing
EOPP_svm = EqOddsPostprocessing(privileged_groups = privileged_groups,
                             unprivileged_groups = unprivileged_groups,
                             seed=40)
EOPP_svm = EOPP_svm.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred_svm_eop = EOPP_svm.predict(data_orig_test_pred)
fair_svm_eo = fair_metrics(filename, data_orig_test, data_transf_test_pred_svm_eop, pred_is_dataset=True)
Computing fairness of the model.
In [283]:
fair_svm_eo
Out[283]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.0 1.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1 0.000000
Gender 0.7 0.817073 1.001747 0.001642 0.013333 -0.007179 -0.024624 [0.9373333333333334] 0.086743
In [284]:
from aif360.algorithms.postprocessing import CalibratedEqOddsPostprocessing
cost_constraint = "fnr"
CPP_svm = CalibratedEqOddsPostprocessing(privileged_groups = privileged_groups,
                                     unprivileged_groups = unprivileged_groups,
                                     cost_constraint=cost_constraint,
                                     seed=40)

CPP_svm = CPP_svm.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred_svm_cp = CPP_svm.predict(data_orig_test_pred)
fair_svm_ceo = fair_metrics(filename, data_orig_test, data_transf_test_pred_svm_cp, pred_is_dataset=True)
Computing fairness of the model.
In [285]:
fair_svm_ceo
Out[285]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.0 0.0 0.000000 0.000000 0.000000 1 0.000000
Gender 0.703333 0.819473 0.8 -0.2 -0.133333 -0.246667 -0.019973 [0.9386666666666666] 0.083249
In [286]:
from aif360.algorithms.postprocessing import RejectOptionClassification
ROC_svm = RejectOptionClassification(privileged_groups = privileged_groups,
                             unprivileged_groups = unprivileged_groups)

ROC_svm = ROC_svm.fit(data_orig_test, data_orig_test_pred)
data_transf_test_pred_svm_roc = ROC_svm.predict(data_orig_test_pred) 
fair_svm_roc = fair_metrics(filename, data_orig_test, data_transf_test_pred_svm_roc, pred_is_dataset=True)
print('SUCCESS: completed 1 model.')
Computing fairness of the model.
SUCCESS: completed 1 model.
In [287]:
fair_svm_roc
Out[287]:
Accuracy F1 DI SPD EOD AOD ERD CNT TI
objective 1.000000 1.000000 1.000000 0.000000 0.00 0.000000 0.000000 1 0.000000
Gender 0.713333 0.765027 0.931889 -0.036115 -0.07 -0.019615 0.059644 [0.7679999999999997] 0.288616
In [ ]: